diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..4429ca2 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,5 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], + import_deps: [:plug] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b20c141 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +plug_signature-*.tar + +plug_signature_example/_build/ +plug_signature_example/deps/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fc58ae9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: elixir +sudo: false +script: mix test +matrix: + include: + - elixir: '1.9.4' + otp_release: '22.2' + - elixir: '1.9.0' + otp_release: '22.0' + - elixir: '1.8.2' + otp_release: '21.3' + - elixir: '1.7.3' + otp_release: '21.1' + - elixir: '1.6.6' + otp_release: '21.0' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0fced40 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2019, Bram Verburg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0598fff --- /dev/null +++ b/README.md @@ -0,0 +1,168 @@ +# PlugSignature + +Plug for verifying request signatures according to the IETF HTTP signatures +[draft specification](https://tools.ietf.org/html/draft-cavage-http-signatures-12). + +Supports the following algorithms: + + * "hs2019", using ECDSA, RSASSA-PSS or HMAC + * "rsa-sha256", using RSASSA-PKCS1-v1_5 + * "ecdsa-sha256" + * "hmac-sha256" + * "rsa-sha1", using RSASSA-PKCS1-v1_5 + +Development and public release of this package were made possible by +[Bluecode](https://bluecode.com/). + +The HTTP Date header parsing module was vendored from +[cowlib](https://github.com/ninenines/cowlib), due to build issues that +prevented use of the package as a dependency. Cowlib is copyright (c) +2013-2018, Loïc Hoguin + +## Usage + +Use `PlugSignature` in a Phoenix (or other Plug-based) application. + +Requests with a valid signature are allowed to proceed while all others are +rejected. Both the success and the failure behaviour can be customized. + +`PlugSignature` requires a callback module that implements the +`PlugSignature.Callback` behaviour. In a Phoenix application this would +typically live in a 'context' module, and it might look something like this: + +```elixir +defmodule MyApp.Auth do + import Ecto.Query, only: [from: 2] + + alias MyApp.Repo + alias MyApp.Auth.AccessKey + + @behaviour PlugSignature.Callback + + @impl true + def client_lookup(key_id, "hs2019", _conn) do + query = from a in AccessKey, + where: a.key_id == ^key_id, + preload: :client + + case Repo.one(query) do + nil -> + {:error, "Invalid access key ID: #{key_id}"} + + {:ok, %AccessKey{revoked: true}} -> + {:error, "Access key revoked: #{key_id}"} + + {:ok, %AccessKey{public_key: pem, client: client}} -> + public_key = X509.PublicKey.from_pem!(pem) + {:ok, client, public_key} + end + end +end +``` + +To enable verification of the request body, through the HTTP Digest header, +add `PlugBodyDigest` from the [plug_body_digest](https://hex.pm/packages/plug_body_digest) +package, e.g. to the application's Phoenix Endpoint: + +```elixir +defmodule MyAppWeb.Endpoint do + # ... + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library(), + body_reader: {PlugBodyDigest, :digest_body_reader, []} + + plug PlugBodyDigest +end +``` + +Finally, add `PlugSignature`, for instance to a Phoenix Router pipeline: + +```elixir +defmodule MyAppWeb.Router do + # ... + + pipeline :api do + plug :accepts, ["json"] + plug PlugSignature, + callback_module: MyApp.Auth, + headers: "(request-target) (created) host digest", + on_success: {PlugSignature, :assign_client, [:client]} + end + + # ... +end +``` + +Alternatively it may be used inside a controller's pipeline, possibly with +guards: + +```elixir +defmodule MyAppWeb.SomeController do + use MyAppWeb, :controller + + plug PlugSignature, [ + callback_module: MyApp.Auth, + headers: "(request-target) (created) host digest" + ] when not action in [:show, :index] + + # ... +end +``` + +The directory `plug_signature_example` in the package source repository +contains a minimal functional sample application, implemented as a simple Plug +server that echos back the request parameters after signature authentication. + +## Client implementation + +The sample application includes clients written in Elixir, using Tesla +middleware, and as a shell script, using OpenSSL and cURL. + +## Installation + +Add `plug_signature` to your list of dependencies in `mix.exs` (and consider +adding `plug_body_digest` as well): + +```elixir +def deps do + [ + {:plug_body_digest, "~> 0.5.0"}, + {:plug_signature, "~> 0.5.0"} + ] +end +``` + +Documentation can be found at [https://hexdocs.pm/plug_signature](https://hexdocs.pm/plug_signature). + +## License + +Copyright (c) 2019, Bram Verburg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/plug_signature.ex b/lib/plug_signature.ex new file mode 100644 index 0000000..d922e66 --- /dev/null +++ b/lib/plug_signature.ex @@ -0,0 +1,411 @@ +defmodule PlugSignature do + @moduledoc """ + Server side implementation of IETF HTTP signature draft + (https://tools.ietf.org/html/draft-cavage-http-signatures-11), as a reusable + Plug. + + Supports the following algorithms: + + * "hs2019", using ECDSA, RSASSA-PSS or HMAC (all with SHA-512) + * "rsa-sha256", using RSASSA-PKCS1-v1_5 + * "ecdsa-sha256" + * "hmac-sha256" + * "rsa-sha1", using RSASSA-PKCS1-v1_5 + + ECDSA signatures with hs2019/ecdsa-sha256 are accepted in ASN.1 or raw + r/s format, for maximum interoperability. + + ## Signature validity time window + + Requests signed according to the "hs2019" algorithm should include a + 'created' and/or 'expires' timestamp. When present in the Authorization + header, the 'created' parameter is checked against the configured validity + (unless it is set to `:infinity`) and the 'expires' parameter is checked + against the current time. + + At least one of these parameters should be included in the signature + calculation, through the respecive pseudo-header. The validity check based + on 'created' and/or 'expires' takes place regardless of whether the + parameters were signed. + + For legacy algorithms, signature validity should be checked using the HTTP + 'Date' header. This happens for all algorithms (including "hs2019") if and + only if when the header value is included in the signature. + + ## Signing the request body + + The HTTP 'Digest' header can be used to protect the integrity of the request + body and include it in the signature. The `PlugSignature` Plug treats the + 'Digest' header as any other header, i.e. it verifies the integrity of the + header value if included in the signature, but it does not verify the + integrity of the request body itself. + + Use a Plug such as [PlugBodyDigest](https://hex.pm/packages/plug_body_digest) + to handle the processing of the 'Digest' header. + + ## Options + + * `:callback_module` (mandatory) - the name of a callback module implementing + the `PlugSignature.Callback` behaviour; this module must implement the + `c:PlugSignature.Callback.client_lookup/3` callback + * `:algorithms` - the signature algorithms, as defined in the IETF + specification; a list containing one or more of: + + * `"hs2019"` (default) + + Legacy algorithms: + + * `"rsa-sha256"` + * `"rsa-sha1"` + * `"ecdsa-sha256"` + * `"hmac-sha256"` + + The first algorithm in the list is considered the default algorithm: if a + client does not specify an algorithm the request is assumed to be signed + using this algorithm + * `:headers` - the minimum set of (pseudo-)headers that need to be signed; + defaults to the request timestamp, taken from the 'created' signature + parameter or the HTTP 'Date' header, depending on the selected algorithm + * `:validity` - a `Range` defining the timeframe (in seconds) after + signing during which the signature is considered valid; set to + `:infinity` to disable, relying solely on the `:expires` parameter; + defaults to `-300..30`, meaning a signature can be up to 5 minutes old, or + up to 30 seconds in the future + * `:legacy` - a keyword list, used to override the `:headers` and/or + `:validity` options for legacy algorithms (those other than "hs2019"); + this may be necessary when these options are set to values not supported + by the legacy algorithms; see the examples below + * `:on_success` - an optional callback for updating the `Plug.Conn` state + upon success; possible values include: + + * `nil` (the default) - do nothing + * `{PlugSignature, :assign_client, [key]}` - assign the client, as + returned by the `c:PlugSignature.Callback.client_lookup/3` callback, + in the `Plug.Conn` struct to the specified key + * `{m, f, a}` - call the function identified by the atom `f` in module + `m`; the function receives the current `Plug.Conn` struct and the + `client` returned by the `c:PlugSignature.Callbacks.client_lookup/3` + callback, along with any additional parameters in the list `a`, and is + expected to return the updated `Plug.Conn` struct + + * `:on_failure` - an optional callback for updating the `Plug.Conn` state + upon failure; possible values include: + + * `{PlugSignature, :failure, []}` (the default) - halt the connection + with an appropriate response; see `failure/3` below + * `{m, f, a}` - call the function identified by the atom `f` in module + `m`; the function receives the current `Plug.Conn` struct, the error + reason (see `t:error_reason/0`),the selected algorithm (a string) and + a list of required headers (strings, for possible use in a + 'WWW-Authenticate' response header), along with any additional + parameters in the list `a`; it is expected to return the updated + `Plug.Conn` struct; see the implementation of `failure/4` for an + example + * `nil` - do nothing + + ## Examples + + # Minimal example relying on defaults: "hs2019" algorithm only, with + # minimal "(created)" headers and default validity: + plug PlugSignature, callback_module: MyApp.SignatureAuth + + # More realistic example with custom header configuration; "hs2019" + # only: + plug PlugSignature, + callback_module: MyApp.SignatureAuth, + headers: "(request-target) (created) host digest" + + # Using legacy algorithms only, with custom header set: + plug PlugSignature, + callback_module: MyApp.SignatureAuth, + algorithms: ["ecdsa-sha256", "rsa-sha256"], + headers: "(request-target) date host" + + # Mix of "hs2019" and legacy algorithms, using 'expires' rather than + # 'created' to verify validity for "hs2019"; the `:legacy` option is + # necessary since the `:headers` and `:validity` values are not valid + # for legacy algoritms: + plug PlugSignature, + callback_module: MyApp.SignatureAuth, + headers: "(request-target) (expires) host digest", + validity: :infinity, + legacy: [ + headers: "(request-target) date host digest", + validity: -300..30, + ] + + """ + + import Plug.Conn + require Logger + alias PlugSignature.Config + alias PlugSignature.Crypto + + @behaviour Plug + + @impl true + @spec init(Keyword.t()) :: Keyword.t() + def init(opts) do + Config.new(opts) + end + + @impl true + @spec call(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t() + def call(conn, opts) do + algorithms = Keyword.fetch!(opts, :algorithms) + default_algorithm = Keyword.fetch!(opts, :default_algorithm) + + # Parse Authorization header and select algorithm + with {:ok, authorization} <- get_authorization_header(conn), + {:ok, signature_opts} <- PlugSignature.Parser.authorization(authorization), + {:ok, algorithm, algorithm_opts} <- + select_algorithm(signature_opts, default_algorithm, algorithms) do + # Ready to call main verification function + verify(conn, algorithm, algorithm_opts, signature_opts, opts) + else + {:error, reason} -> + headers = algorithms[default_algorithm].headers + on_failure(opts[:on_failure], conn, reason, default_algorithm, headers) + end + end + + defp get_authorization_header(conn) do + case get_req_header(conn, "authorization") do + [authorization | _] -> {:ok, authorization} + _otherwise -> {:error, "no authorization header"} + end + end + + defp select_algorithm(signature_opts, default_algorithm, algorithms) do + algorithm = Keyword.get(signature_opts, :algorithm, default_algorithm) + + case algorithms[algorithm] do + nil -> + {:error, "bad signing algorithm #{algorithm}"} + + algorithm_opts -> + {:ok, algorithm, algorithm_opts} + end + end + + defp verify(conn, algorithm, algorithm_opts, signature_opts, opts) do + with {:ok, header_list, signature} <- handle_signature_opts(signature_opts, algorithm_opts), + # Check validity based on timestamp and/or date header + :ok <- verify_signature_timestamp(signature_opts, algorithm_opts.validity), + :ok <- verify_signature_expiry(signature_opts), + :ok <- + verify_date_header(conn, algorithm_opts.validity, algorithm_opts.check_date_header), + # Look up client and credentials + key_id = Keyword.get(signature_opts, :key_id), + {:ok, client, credentials} <- + opts[:callback_module].client_lookup(key_id, algorithm, conn), + # Build string to sign based on headers and algorithm + {:ok, signature_string} <- + build_signature_string(conn, signature_opts, algorithm, header_list), + # Verify the signature + {:ok, true} <- Crypto.verify(signature_string, algorithm, signature, credentials) do + # All checks passed: continue + on_success(opts[:on_success], conn, client) + else + {:ok, false} -> + reason = "incorrect signature or HMAC" + on_failure(opts[:on_failure], conn, reason, algorithm, algorithm_opts.headers) + + {:error, reason} -> + on_failure(opts[:on_failure], conn, reason, algorithm, algorithm_opts.headers) + end + end + + defp handle_signature_opts(signature_opts, algorithm_opts) do + # - keyId is mandatory, though we do not need it here + # - signature is mandatory + # - headers is optional, at least the expected headers must be present + with {:ok, _key_id} <- fetch(signature_opts, :key_id), + {:ok, signature_b64} <- fetch(signature_opts, :signature), + {:ok, signature} <- decode64(signature_b64) do + headers = Keyword.get(signature_opts, :headers, algorithm_opts.default_headers) + header_list = headers |> String.downcase() |> String.split(" ") + + case algorithm_opts.header_list -- header_list do + [] -> + {:ok, header_list, signature} + + missing_headers -> + {:error, "insufficient signature coverage: #{Enum.join(missing_headers, ", ")}"} + end + end + end + + defp fetch(keyword_list, key) do + case Keyword.fetch(keyword_list, key) do + :error -> {:error, "key #{key} not found"} + success -> success + end + end + + defp decode64(b64) do + case Base.decode64(b64) do + :error -> {:error, "Base64 decoding failed"} + success -> success + end + end + + defp verify_signature_timestamp(signature_opts, validity) do + with {:ok, created} <- Keyword.fetch(signature_opts, :created), + {unix_int, ""} <- Integer.parse(created) do + unix_int + |> DateTime.from_unix!() + |> verify_validity(validity) + else + :error -> + # Missing 'created' parameter, cannot verify validity + :ok + + _error -> + {:error, "malformed signature creation timestamp"} + end + end + + defp verify_signature_expiry(signature_opts) do + with {:ok, expires_str} <- Keyword.fetch(signature_opts, :expires), + {expires, ""} <- Integer.parse(expires_str) do + now = DateTime.utc_now() |> DateTime.to_unix() + + if now > expires do + {:error, "request expired"} + else + :ok + end + else + :error -> + # Missing 'expires' parameter, cannot verify validity + :ok + + _error -> + {:error, "malformed signature expiry timestamp"} + end + end + + defp verify_date_header(_conn, _validity, false), do: :ok + defp verify_date_header(_conn, :infinity, true), do: :ok + + defp verify_date_header(conn, validity, true) do + case get_req_header(conn, "date") do + [date] -> + date + |> :plug_signature_http_date.parse_date() + |> NaiveDateTime.from_erl!() + |> DateTime.from_naive!("Etc/UTC") + |> verify_validity(validity) + + _otherwise -> + {:error, "missing Date header"} + end + end + + defp verify_validity(_date_time, :infinity), do: :ok + + defp verify_validity(date_time, past..future) do + age = DateTime.diff(date_time, DateTime.utc_now()) + + cond do + age < past -> {:error, "request expired"} + age > future -> {:error, "request timestamp in the future"} + true -> :ok + end + end + + defp build_signature_string(conn, signature_opts, algorithm, header_list) do + signature_string = + Enum.map_join(header_list, "\n", &header_part(conn, signature_opts, algorithm, &1)) + + {:ok, signature_string} + rescue + # Handle the case where (created) or (expires) pseudo header is not + # available or not supported by the algorithm + _key_error -> {:error, "could not build signature_string"} + end + + defp header_part(conn, _signature_opts, _algorithm, "(request-target)") do + query_part = + case conn.query_string do + "" -> "" + query -> "?#{query}" + end + + "(request-target): #{String.downcase(conn.method)} #{conn.request_path}#{query_part}" + end + + defp header_part(_conn, signature_opts, "hs2019", "(created)") do + "(created): #{Keyword.fetch!(signature_opts, :created)}" + end + + defp header_part(_conn, signature_opts, "hs2019", "(expires)") do + "(expires): #{Keyword.fetch!(signature_opts, :expires)}" + end + + defp header_part(conn, _signature_opts, _algorithm, header) + when header not in ["(created)", "(expires)"] do + header_name = String.downcase(header) + + value = + conn + |> get_req_header(header_name) + |> Enum.map(&String.trim/1) + |> Enum.join(", ") + + if value == "", do: raise("Missing header") + + "#{header_name}: #{value}" + end + + defp on_success(nil, conn, _client), do: conn + defp on_success({m, f, a}, conn, client), do: apply(m, f, [conn, client | a]) + defp on_success(fun, conn, client) when is_function(fun, 2), do: fun.(conn, client) + defp on_success(fun, conn, _client) when is_function(fun, 1), do: fun.(conn) + + defp on_failure(nil, conn, _reason, _algorithm, _headers), do: conn + + defp on_failure({m, f, a}, conn, reason, algorithm, headers), + do: apply(m, f, [conn, reason, algorithm, headers | a]) + + defp on_failure(fun, conn, reason, algorithm, headers) when is_function(fun, 4), + do: fun.(conn, reason, algorithm, headers) + + defp on_failure(fun, conn, reason, algorithm, _headers) when is_function(fun, 3), + do: fun.(conn, reason, algorithm) + + defp on_failure(fun, conn, reason, _algorithm, _headers) when is_function(fun, 2), + do: fun.(conn, reason) + + @doc """ + Success function that assigns the authenticated client under the specified + key in the `Plug.Conn` struct. + """ + @spec assign_client(Plug.Conn.t(), any(), atom()) :: Plug.Conn.t() + def assign_client(conn, client, field_name) do + assign(conn, field_name, client) + end + + @doc """ + The default failure function. + + It logs the failure reason, returns a 401 'Unauthorized' response with a + 'WWW-Authenticate' response header listing the supported algorithms, and + halts the connection. + """ + @spec failure(Plug.Conn.t(), String.t(), String.t(), String.t()) :: Plug.Conn.t() + def failure(conn, reason, algorithm, headers) do + Logger.info("Request unauthorized: #{reason}") + + # We do not expose the exact reason to the client, just a generic 401, to + # avoid leaking information that may help an attacker + conn + |> put_resp_header( + "www-authenticate", + ~s(Signature algorithm=#{algorithm},headers="#{headers}") + ) + |> send_resp(401, "") + |> halt() + end +end diff --git a/lib/plug_signature/callback.ex b/lib/plug_signature/callback.ex new file mode 100644 index 0000000..5775639 --- /dev/null +++ b/lib/plug_signature/callback.ex @@ -0,0 +1,33 @@ +defmodule PlugSignature.Callback do + @moduledoc """ + Behaviour for the callback module that implements client and credential + lookup for `PlugSignature`. + + ## Example + + defmodule MyApp.SignatureAuth do + @behaviour PlugSignature.Callback + + @impl true + def client_lookup(key_id, "hs2019", _conn) do + # ... + {:ok, client, client.hmac_secret} + end + end + """ + + @doc """ + Takes the keyId from the parsed Authorization header, the algorithm name and + the `Plug.Conn` struct, and returns a success or error tuple. + + In case of success, an application-specific term is returned that identifies + the client, along with that client's credentials (a public key or HMAC + secret). + + The `Plug.Conn` struct may be used to select the relevant client, but it + cannot be modified. For instance, the hostname of the request may be needed + to select the correct client in a multi-tennant application. + """ + @callback client_lookup(key_id :: binary(), algorithm :: binary(), conn :: Plug.Conn.t()) :: + {:ok, any(), :public_key.public_key() | binary()} | {:error, String.t()} +end diff --git a/lib/plug_signature/config.ex b/lib/plug_signature/config.ex new file mode 100644 index 0000000..591e573 --- /dev/null +++ b/lib/plug_signature/config.ex @@ -0,0 +1,191 @@ +defmodule PlugSignature.ConfigError do + @moduledoc """ + Exception raised in case of an invalid configuration. + """ + + defexception [:message] +end + +defmodule PlugSignature.Config do + @moduledoc false + + @default_algorithms ["hs2019"] + @hs2019_default_headers "(created)" + @legacy_default_headers "date" + @default_validity -300..30 + @default_on_success nil + @default_on_failure {PlugSignature, :failure, []} + + @legacy_algorithms ["rsa-sha1", "rsa-sha256", "hmac-sha256", "ecdsa-sha256"] + + @type on_success :: + nil + | {module(), atom(), [term()]} + | (Plug.Conn.t(), term() -> Plug.Conn.t()) + | (Plug.Conn.t() -> Plug.Conn.t()) + @type on_failure :: + nil + | {module(), atom(), [term()]} + | (Plug.Conn.t(), String.t(), String.t(), [String.t()] -> Plug.Conn.t()) + | (Plug.Conn.t(), String.t(), String.t() -> Plug.Conn.t()) + | (Plug.Conn.t(), String.t() -> Plug.Conn.t()) + + # Used to validate the `PlugSignature` configuration; since the arguments to + # the `PlugSignature.init/1` are typically evaluated at compile-time, this + # can help catch configuration issues early + @spec new(Keyword.t()) :: [ + {:callback_module, Module.t()} + | {:default_algorithm, String.t()} + | {:algorithms, map()} + | {:on_success, on_success()} + | {:on_failure, on_failure()} + ] + def new(opts) do + callback_module = + Keyword.get(opts, :callback_module) || + raise PlugSignature.ConfigError, "missing mandatory option `:callback_module`" + + algorithms = Keyword.get(opts, :algorithms, @default_algorithms) + + if algorithms == [] do + raise PlugSignature.ConfigError, "algorithm list is empty" + end + + on_success = opts |> Keyword.get(:on_success, @default_on_success) |> validate_on_success() + on_failure = opts |> Keyword.get(:on_failure, @default_on_failure) |> validate_on_failure() + + algorithm_config = + algorithms + |> Enum.map(&{&1, opts_for_algorithm(&1, opts)}) + |> Enum.into(%{}) + + [ + callback_module: callback_module, + default_algorithm: hd(algorithms), + algorithms: algorithm_config, + on_success: on_success, + on_failure: on_failure + ] + end + + # Expand and validate options per algorithm; raise on unknown algorithm or + # invalid configuration + + defp opts_for_algorithm("hs2019", opts) do + headers = Keyword.get(opts, :headers, @hs2019_default_headers) + header_list = headers |> String.downcase() |> String.split(" ") + validate_header_list!(header_list, "hs2019") + validity = Keyword.get(opts, :validity, @default_validity) + + case validity do + :infinity -> + if "(expires)" not in header_list do + raise PlugSignature.ConfigError, "missing pseudo-header `(expires)` in header list" + end + + _from.._to -> + :ok + + _otherwise -> + raise PlugSignature.ConfigError, "validity must be a range, or `:infinity`" + end + + %{ + headers: headers, + header_list: header_list, + default_headers: @hs2019_default_headers, + validity: validity, + check_date_header: "date" in header_list + } + end + + defp opts_for_algorithm(algorithm, opts) when algorithm in @legacy_algorithms do + legacy_opts = Keyword.get(opts, :legacy, []) + + headers = + Keyword.get(legacy_opts, :headers, Keyword.get(opts, :headers, @legacy_default_headers)) + + header_list = headers |> String.downcase() |> String.split(" ") + validate_header_list!(header_list, algorithm) + + validity = + Keyword.get(legacy_opts, :validity, Keyword.get(opts, :validity, @default_validity)) + + case validity do + :infinity -> + raise PlugSignature.ConfigError, "cannot use infinite validity with legacy algorithms" + + _from.._to -> + :ok + + _otherwise -> + raise PlugSignature.ConfigError, "validity must be a range" + end + + %{ + headers: headers, + header_list: header_list, + default_headers: @legacy_default_headers, + validity: validity, + check_date_header: "date" in header_list + } + end + + defp opts_for_algorithm(algorithm, _opts) do + raise PlugSignature.ConfigError, "unknown algorithm: '#{algorithm}'" + end + + # Make sure all header list values are legal HTTP header names or known + # pseudo-headers for the specific algorithm + + defp validate_header_list!([], _algorithm) do + raise PlugSignature.ConfigError, "header list must not be empty" + end + + defp validate_header_list!(headers, algorithm) do + headers + |> Enum.reject(&is_standard_header?/1) + |> validate_pseudo_header_list!(algorithm) + end + + defp validate_pseudo_header_list!([], _algorithm), do: :ok + + defp validate_pseudo_header_list!(["(request-target)" | more], algorithm) do + validate_pseudo_header_list!(more, algorithm) + end + + defp validate_pseudo_header_list!(["(created)" | more], "hs2019" = algorithm) do + validate_pseudo_header_list!(more, algorithm) + end + + defp validate_pseudo_header_list!(["(expires)" | more], "hs2019" = algorithm) do + validate_pseudo_header_list!(more, algorithm) + end + + defp validate_pseudo_header_list!([header | _], _algorithm) do + raise PlugSignature.ConfigError, "invalid header '#{header}'" + end + + defp is_standard_header?(header) do + header =~ ~r/^[!#-'*+\-.0-9^-z|~]+$/ + end + + defp validate_on_success(nil), do: nil + defp validate_on_success({m, f, a}) when is_atom(m) and is_atom(f) and is_list(a), do: {m, f, a} + defp validate_on_success(fun) when is_function(fun, 2) or is_function(fun, 1), do: fun + + defp validate_on_success(_) do + raise PlugSignature.ConfigError, "invalid value for `:on_success`" + end + + defp validate_on_failure(nil), do: nil + defp validate_on_failure({m, f, a}) when is_atom(m) and is_atom(f) and is_list(a), do: {m, f, a} + + defp validate_on_failure(fun) + when is_function(fun, 4) or is_function(fun, 2) or is_function(fun, 1), + do: fun + + defp validate_on_failure(_) do + raise PlugSignature.ConfigError, "invalid value for `:on_failure`" + end +end diff --git a/lib/plug_signature/conn_test.ex b/lib/plug_signature/conn_test.ex new file mode 100644 index 0000000..d3d4606 --- /dev/null +++ b/lib/plug_signature/conn_test.ex @@ -0,0 +1,201 @@ +defmodule PlugSignature.ConnTest do + @moduledoc """ + Helpers for testing HTTP signatures with Plug/Phoenix. + """ + + import Plug.Conn + + alias PlugSignature.Crypto + + @doc """ + Adds an Authorization header with a signature. Requires a secret (RSA + private key, EC private key or HMAC shared secret) and key ID. + + ## Options + + * `:algorithms` - the HTTP signature algorithms to be used; list with + one or more of: + + * `"hs2019"` (default) + * `"rsa-sha256"` + * `"rsa-sha1"` + * `"ecdsa-sha256"` + * `"hmac-sha256"` + + The first algorithm in the list will be used to generate the signature + (it is a list to allow the core set of configuration options to be + shared with `PlugSignature` in tests). + * `:headers` - set the list of HTTP (pseudo) headers to sign; defaults to + "(created)" (which is only valid when the algorithm is "hs2019") + * `:request_target` - explicitly set the request target; by default it is + built from the Plug.Conn struct (method, request_path and query) + * `:age` - shift the HTTP Date header and the signature's 'created' + parameter by the given number of seconds into the past; defaults to 0 + * `:created` - set the signature's 'created' parameter (overrides `:age`); + set to a empty string to omit the 'created' parameter + * `:date` - set the HTTP Date header (overrides `:age`) + * `:expires_in` - if set, adds an 'expires' parameter with a timestamp + the given number of seconds in the future + * `:expires` - set the signature's 'expires' parameter (overrides + `:expires_in`) + * `:key_id_override` - override the value for `keyId` in the Authorization + header + * `:algorithm_override` - override the value for the signature's + 'algorithm' parameter in the Authorization header + * `:signature_override` - override the signature value sent in the + Authorization header + * `:headers_override` - override the value for the signature's 'headers' + parameter in the Authorization header + * `:created_override` - override the value for the signature's 'created' + parameter in the Authorization header + * `:expires_override` - override the value for the signature's 'expires' + parameter in the Authorization header + """ + def with_signature(conn, key, key_id, opts \\ []) do + request_target = + Keyword.get_lazy(opts, :request_target, fn -> + method = conn.method |> to_string |> String.downcase() + + case conn.query_string do + "" -> + "#{method} #{conn.request_path}" + + query -> + "#{method} #{conn.request_path}?#{query}" + end + end) + + age = Keyword.get(opts, :age, 0) + created = Keyword.get_lazy(opts, :created, fn -> created(age) end) + date = Keyword.get_lazy(opts, :date, fn -> http_date(age) end) + expires_in = Keyword.get(opts, :expires_in, nil) + expires = Keyword.get_lazy(opts, :expires, fn -> expires(expires_in) end) + algorithms = Keyword.get(opts, :algorithms, ["hs2019"]) + algorithm = hd(algorithms) + headers = Keyword.get(opts, :headers, default_headers(algorithm)) + + to_be_signed = + Keyword.get_lazy(opts, :to_be_signed, fn -> + headers + |> String.split(" ") + |> Enum.map(fn + "(request-target)" -> "(request-target): #{request_target}" + "(created)" -> "(created): #{created}" + "(expires)" -> "(expires): #{expires}" + "date" -> "date: #{date}" + header -> "#{header}: #{get_req_header(conn, header) |> Enum.join(",")}" + end) + |> Enum.join("\n") + end) + + signature = + Keyword.get_lazy(opts, :signature, fn -> + {:ok, signature} = Crypto.sign(to_be_signed, algorithm, key) + Base.encode64(signature) + end) + + authorization = + [ + ~s(keyId="#{Keyword.get(opts, :key_id_override, key_id)}"), + ~s(signature="#{Keyword.get(opts, :signature_override, signature)}"), + ~s(headers="#{Keyword.get(opts, :headers_override, headers)}"), + ~s(created=#{Keyword.get(opts, :created_override, created)}), + ~s(expires=#{Keyword.get(opts, :expires_override, expires)}), + ~s(algorithm=#{Keyword.get(opts, :algorithm_override, algorithm)}) + ] + |> Enum.reject(&Regex.match?(~r/^[^=]+=("")?$/, &1)) + |> Enum.join(",") + + conn + |> put_req_header("date", date) + |> put_req_header("authorization", "Signature #{authorization}") + end + + defp default_headers("hs2019"), do: "(created)" + defp default_headers(_legacy), do: "date" + + @doc """ + Add an HTTP Digest header (RFC3230, section 4.3.2). + + When the request body is passed in as a binary, a SHA-256 digest of the body + is calculated and added as part of the header. Alternatively, a map of + digest types and values may be provided. + """ + def with_digest(conn, body_or_digests) + + def with_digest(conn, digests) when is_map(digests) do + digest_header = + digests + |> Enum.map(fn {alg, value} -> "#{alg}=#{value}" end) + |> Enum.join(",") + + put_req_header(conn, "digest", digest_header) + end + + def with_digest(conn, body) do + with_digest(conn, %{"SHA-256" => :crypto.hash(:sha256, body) |> Base.encode64()}) + end + + @http_methods [:get, :post, :put, :patch, :delete, :options, :connect, :trace, :head] + + for method <- @http_methods do + function = :"signed_#{method}" + + @doc """ + Makes a signed request to a Phoenix endpoint. + + The last argument is a keyword list with signature options, with the + `:key` and `:key_id` options being mandatory. For other options, please + see `with_signature/4`. + + Requires Phoenix.ConnTest. + """ + defmacro unquote(function)(conn, path, params_or_body \\ nil, sign_opts) do + raise_on_missing_phoenix_conntest!() + + method = unquote(method) + + quote do + is_binary(unquote(path)) || raise "Signed requests must specify a request path" + + key = Keyword.fetch!(unquote(sign_opts), :key) + key_id = Keyword.fetch!(unquote(sign_opts), :key_id) + + %{unquote(conn) | method: unquote(method), request_path: unquote(path)} + |> with_signature(key, key_id, unquote(sign_opts)) + |> Phoenix.ConnTest.dispatch( + @endpoint, + unquote(method), + unquote(path), + unquote(params_or_body) + ) + end + end + end + + defp created(nil), do: "" + + defp created(age) do + now = DateTime.utc_now() |> DateTime.to_unix() + to_string(now - age) + end + + defp http_date(age) do + NaiveDateTime.utc_now() + |> NaiveDateTime.add(0 - age) + |> NaiveDateTime.to_erl() + |> :plug_signature_http_date.rfc7231() + end + + defp expires(nil), do: "" + + defp expires(validity) do + now = DateTime.utc_now() |> DateTime.to_unix() + to_string(now + validity) + end + + defp raise_on_missing_phoenix_conntest! do + Code.ensure_loaded?(Phoenix.ConnTest) || + raise "endpoint testing requirs Phoenix.ConnTest" + end +end diff --git a/lib/plug_signature/crypto.ex b/lib/plug_signature/crypto.ex new file mode 100644 index 0000000..3d339b9 --- /dev/null +++ b/lib/plug_signature/crypto.ex @@ -0,0 +1,152 @@ +defmodule PlugSignature.Crypto do + @moduledoc """ + This module exposes the cryptographic core functions used in HTTP signatures. + These functions may be used to implement clients, or alternative server-side + implementations, e.g. for Raxx. + + Supported algorithms: + + * 'hs2019', using ECDSA, RSASSA-PSS or HMAC (all with SHA-512) + * 'rsa-sha256', using RSASSA-PKCS1-v1_5 + * 'rsa-sha1', using RSASSA-PKCS1-v1_5 + * 'ecdsa-sha256' + * 'hmac-sha256' + """ + + require Record + + Record.defrecordp( + :rsa_public_key, + :RSAPublicKey, + Record.extract(:RSAPublicKey, from_lib: "public_key/include/OTP-PUB-KEY.hrl") + ) + + Record.defrecordp( + :rsa_private_key, + :RSAPrivateKey, + Record.extract(:RSAPrivateKey, from_lib: "public_key/include/OTP-PUB-KEY.hrl") + ) + + Record.defrecordp( + :ec_private_key, + :ECPrivateKey, + Record.extract(:ECPrivateKey, from_lib: "public_key/include/OTP-PUB-KEY.hrl") + ) + + Record.defrecordp( + :ecdsa_signature, + :"ECDSA-Sig-Value", + Record.extract(:"ECDSA-Sig-Value", from_lib: "public_key/include/OTP-PUB-KEY.hrl") + ) + + @doc """ + Verifies a signature value. Raises in case of errors. + """ + def verify!(payload, "hs2019", signature, rsa_public_key(publicExponent: e, modulus: n)) do + # Use PSS padding; requires workaround for https://bugs.erlang.org/browse/ERL-878 + :crypto.verify(:rsa, :sha512, payload, signature, [e, n], rsa_padding: :rsa_pkcs1_pss_padding) + end + + def verify!(payload, "hs2019", signature, {_point, _ecpk_parameters} = public_key) do + signature = der_ecdsa_signature(signature) + + :public_key.verify(payload, :sha512, signature, public_key) + end + + def verify!(payload, "hs2019", signature, hmac_secret) when is_binary(hmac_secret) do + signature == :crypto.hmac(:sha512, hmac_secret, payload) + end + + def verify!(payload, "rsa-sha256", signature, rsa_public_key() = public_key) do + # Use PKCS1-v1_5 padding (default) + :public_key.verify(payload, :sha256, signature, public_key) + end + + def verify!(payload, "rsa-sha1", signature, rsa_public_key() = public_key) do + # Use PKCS1-v1_5 padding (default) + :public_key.verify(payload, :sha, signature, public_key) + end + + def verify!(payload, "ecdsa-sha256", signature, {_point, _ecpk_parameters} = public_key) do + signature = der_ecdsa_signature(signature) + + :public_key.verify(payload, :sha256, signature, public_key) + end + + def verify!(payload, "hmac-sha256", signature, hmac_secret) when is_binary(hmac_secret) do + signature == :crypto.hmac(:sha256, hmac_secret, payload) + end + + def verify(payload, algorithm, signature, public_key) do + {:ok, verify!(payload, algorithm, signature, public_key)} + rescue + _ -> {:error, "bad algorithm or key"} + end + + # Not all ECDSA implementations wrap the signature in ASN.1; some just + # return the raw curve coordinates (r and s) concattenated as binaries; + # so we convert to ASN.1 DER format if necessary + defp der_ecdsa_signature(signature) do + case is_der_ecdsa_signature?(signature) do + true -> + signature + + false -> + size = signature |> byte_size() |> div(2) + <> = signature + :public_key.der_encode(:"ECDSA-Sig-Value", ecdsa_signature(r: r, s: s)) + end + end + + defp is_der_ecdsa_signature?(signature) do + _ = :public_key.der_decode(:"ECDSA-Sig-Value", signature) + true + rescue + MatchError -> false + end + + @doc """ + Generates a signature. Raises in case of an error. + """ + def sign!(payload, "hs2019", rsa_private_key(publicExponent: e, modulus: n, privateExponent: d)) do + # Use PSS padding; requires workaround for https://bugs.erlang.org/browse/ERL-878 + :crypto.sign(:rsa, :sha512, payload, [e, n, d], rsa_padding: :rsa_pkcs1_pss_padding) + end + + def sign!(payload, "hs2019", ec_private_key() = private_key) do + :public_key.sign(payload, :sha512, private_key) + end + + def sign!(payload, "hs2019", hmac_secret) when is_binary(hmac_secret) do + :crypto.hmac(:sha512, hmac_secret, payload) + end + + def sign!(payload, "rsa-sha256", rsa_private_key() = private_key) do + # Use PKCS1-v1_5 padding (default) + :public_key.sign(payload, :sha256, private_key) + end + + def sign!(payload, "rsa-sha1", rsa_private_key() = private_key) do + # Use PKCS1-v1_5 padding (default) + :public_key.sign(payload, :sha, private_key) + end + + def sign!(payload, "ecdsa-sha256", ec_private_key() = private_key) do + :public_key.sign(payload, :sha256, private_key) + end + + def sign!(payload, "hmac-sha256", hmac_secret) when is_binary(hmac_secret) do + :crypto.hmac(:sha256, hmac_secret, payload) + end + + @doc """ + Generates a signature. + + Returns `{:ok, signature}` or `{:error, reason}`. + """ + def sign(payload, algorithm, private_key) do + {:ok, sign!(payload, algorithm, private_key)} + rescue + _ -> {:error, "bad algorithm or key"} + end +end diff --git a/lib/plug_signature/parser.ex b/lib/plug_signature/parser.ex new file mode 100644 index 0000000..3051c0f --- /dev/null +++ b/lib/plug_signature/parser.ex @@ -0,0 +1,4089 @@ +# Generated from lib/plug_signature/parser.ex.exs, do not edit. +# Generated at 2020-01-01 07:40:41Z. + +defmodule PlugSignature.Parser do + @moduledoc false + + # credo:disable-for-this-file + + @spec authorization_parser(binary, keyword) :: + {:ok, [term], rest, context, line, byte_offset} + | {:error, reason, rest, context, line, byte_offset} + when line: {pos_integer, byte_offset}, + byte_offset: pos_integer, + rest: binary, + reason: String.t(), + context: map() + defp authorization_parser(binary, opts \\ []) when is_binary(binary) do + line = Keyword.get(opts, :line, 1) + offset = Keyword.get(opts, :byte_offset, 0) + context = Map.new(Keyword.get(opts, :context, [])) + + case(authorization_parser__0(binary, [], [], context, {line, offset}, offset)) do + {:ok, acc, rest, context, line, offset} -> + {:ok, :lists.reverse(acc), rest, context, line, offset} + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp authorization_parser__0( + <<"Signature", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__1(rest, [] ++ acc, stack, context, comb__line, comb__offset + 9) + end + + defp authorization_parser__0(rest, _acc, _stack, context, line, offset) do + {:error, "expected string \"Signature\"", rest, context, line, offset} + end + + defp authorization_parser__1(rest, acc, stack, context, line, offset) do + authorization_parser__5( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__3(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__2(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__4(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__3(rest, [], stack, context, line, offset) + end + + defp authorization_parser__5(rest, acc, stack, context, line, offset) do + authorization_parser__6(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__6( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 do + authorization_parser__7(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__6(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__4(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__7( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 do + authorization_parser__9(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__7(rest, acc, stack, context, line, offset) do + authorization_parser__8(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__9(rest, acc, stack, context, line, offset) do + authorization_parser__7(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__8(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__10(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__10(rest, acc, stack, context, line, offset) do + authorization_parser__14( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__12(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__11(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__13(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__12(rest, [], stack, context, line, offset) + end + + defp authorization_parser__14(rest, acc, stack, context, line, offset) do + authorization_parser__166( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__16(rest, acc, stack, context, line, offset) do + authorization_parser__17(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__17( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__18(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__17(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__13(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__18( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__20(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__18(rest, acc, stack, context, line, offset) do + authorization_parser__19(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__20(rest, acc, stack, context, line, offset) do + authorization_parser__18(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__19( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__22(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__19(rest, acc, stack, context, line, offset) do + authorization_parser__21(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__22(rest, acc, stack, context, line, offset) do + authorization_parser__19(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__21( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__23(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__21(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__13(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__23( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__25(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__23(rest, acc, stack, context, line, offset) do + authorization_parser__24(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__25(rest, acc, stack, context, line, offset) do + authorization_parser__23(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__24(rest, acc, stack, context, line, offset) do + authorization_parser__35( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__27( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__28(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__27(rest, _acc, stack, context, line, offset) do + [_, _, _, _, acc | stack] = stack + authorization_parser__13(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__28(rest, acc, stack, context, line, offset) do + authorization_parser__30( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__30( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__31(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__30( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__31(rest, acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__30(rest, acc, stack, context, line, offset) do + authorization_parser__29(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__29(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__32(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__31( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__30( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__32( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__33(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__32(rest, _acc, stack, context, line, offset) do + [_, _, _, _, acc | stack] = stack + authorization_parser__13(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__33(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__26(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__34(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__27(rest, [], stack, context, line, offset) + end + + defp authorization_parser__35( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__36(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__35(rest, acc, stack, context, line, offset) do + authorization_parser__34(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__36( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__38(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__36(rest, acc, stack, context, line, offset) do + authorization_parser__37(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__38(rest, acc, stack, context, line, offset) do + authorization_parser__36(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__37(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__26(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__26(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__39(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__39(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__15(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__40(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__16(rest, [], stack, context, line, offset) + end + + defp authorization_parser__41(rest, acc, stack, context, line, offset) do + authorization_parser__42(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__42( + <<"headers", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__43(rest, acc, stack, context, comb__line, comb__offset + 7) + end + + defp authorization_parser__42(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__40(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__43( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__45(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__43(rest, acc, stack, context, line, offset) do + authorization_parser__44(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__45(rest, acc, stack, context, line, offset) do + authorization_parser__43(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__44( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__46(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__44(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__40(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__46( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__48(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__46(rest, acc, stack, context, line, offset) do + authorization_parser__47(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__48(rest, acc, stack, context, line, offset) do + authorization_parser__46(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__47(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__49(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__49(rest, acc, stack, context, line, offset) do + authorization_parser__50(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__50(rest, acc, stack, context, line, offset) do + authorization_parser__60( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__52( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__53(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__52(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__40(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__53(rest, acc, stack, context, line, offset) do + authorization_parser__55( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__55( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__56(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__55( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__56(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__55(rest, acc, stack, context, line, offset) do + authorization_parser__54(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__54(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__57(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__56( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__55( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__57( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__58(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__57(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__40(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__58(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__51(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__59(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__52(rest, [], stack, context, line, offset) + end + + defp authorization_parser__60( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__61(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__60(rest, acc, stack, context, line, offset) do + authorization_parser__59(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__61( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__63(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__61(rest, acc, stack, context, line, offset) do + authorization_parser__62(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__63(rest, acc, stack, context, line, offset) do + authorization_parser__61(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__62(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__51(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__51(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__64( + rest, + [headers: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__64(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__15(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__65(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__41(rest, [], stack, context, line, offset) + end + + defp authorization_parser__66(rest, acc, stack, context, line, offset) do + authorization_parser__67(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__67( + <<"expires", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__68(rest, acc, stack, context, comb__line, comb__offset + 7) + end + + defp authorization_parser__67(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__65(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__68( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__70(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__68(rest, acc, stack, context, line, offset) do + authorization_parser__69(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__70(rest, acc, stack, context, line, offset) do + authorization_parser__68(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__69( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__71(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__69(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__65(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__71( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__73(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__71(rest, acc, stack, context, line, offset) do + authorization_parser__72(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__73(rest, acc, stack, context, line, offset) do + authorization_parser__71(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__72(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__74(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__74(rest, acc, stack, context, line, offset) do + authorization_parser__75(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__75(rest, acc, stack, context, line, offset) do + authorization_parser__85( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__77( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__78(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__77(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__65(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__78(rest, acc, stack, context, line, offset) do + authorization_parser__80( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__80( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__81(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__80( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__81(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__80(rest, acc, stack, context, line, offset) do + authorization_parser__79(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__79(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__82(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__81( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__80( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__82( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__83(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__82(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__65(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__83(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__76(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__84(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__77(rest, [], stack, context, line, offset) + end + + defp authorization_parser__85( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__86(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__85(rest, acc, stack, context, line, offset) do + authorization_parser__84(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__86( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__88(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__86(rest, acc, stack, context, line, offset) do + authorization_parser__87(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__88(rest, acc, stack, context, line, offset) do + authorization_parser__86(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__87(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__76(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__76(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__89( + rest, + [expires: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__89(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__15(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__90(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__66(rest, [], stack, context, line, offset) + end + + defp authorization_parser__91(rest, acc, stack, context, line, offset) do + authorization_parser__92(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__92( + <<"created", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__93(rest, acc, stack, context, comb__line, comb__offset + 7) + end + + defp authorization_parser__92(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__90(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__93( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__95(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__93(rest, acc, stack, context, line, offset) do + authorization_parser__94(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__95(rest, acc, stack, context, line, offset) do + authorization_parser__93(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__94( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__96(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__94(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__90(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__96( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__98(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__96(rest, acc, stack, context, line, offset) do + authorization_parser__97(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__98(rest, acc, stack, context, line, offset) do + authorization_parser__96(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__97(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__99(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__99(rest, acc, stack, context, line, offset) do + authorization_parser__100(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__100(rest, acc, stack, context, line, offset) do + authorization_parser__110( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__102( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__103(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__102(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__90(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__103(rest, acc, stack, context, line, offset) do + authorization_parser__105( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__105( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__106(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__105( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__106(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__105(rest, acc, stack, context, line, offset) do + authorization_parser__104(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__104(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__107(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__106( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__105( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__107( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__108(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__107(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__90(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__108(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__101(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__109(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__102(rest, [], stack, context, line, offset) + end + + defp authorization_parser__110( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__111(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__110(rest, acc, stack, context, line, offset) do + authorization_parser__109(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__111( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__113(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__111(rest, acc, stack, context, line, offset) do + authorization_parser__112(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__113(rest, acc, stack, context, line, offset) do + authorization_parser__111(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__112(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__101(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__101(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__114( + rest, + [created: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__114(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__15(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__115(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__91(rest, [], stack, context, line, offset) + end + + defp authorization_parser__116(rest, acc, stack, context, line, offset) do + authorization_parser__117(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__117( + <<"algorithm", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__118(rest, acc, stack, context, comb__line, comb__offset + 9) + end + + defp authorization_parser__117(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__115(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__118( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__120(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__118(rest, acc, stack, context, line, offset) do + authorization_parser__119(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__120(rest, acc, stack, context, line, offset) do + authorization_parser__118(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__119( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__121(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__119(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__115(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__121( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__123(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__121(rest, acc, stack, context, line, offset) do + authorization_parser__122(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__123(rest, acc, stack, context, line, offset) do + authorization_parser__121(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__122(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__124(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__124(rest, acc, stack, context, line, offset) do + authorization_parser__125(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__125(rest, acc, stack, context, line, offset) do + authorization_parser__135( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__127( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__128(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__127(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__115(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__128(rest, acc, stack, context, line, offset) do + authorization_parser__130( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__130( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__131(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__130( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__131(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__130(rest, acc, stack, context, line, offset) do + authorization_parser__129(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__129(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__132(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__131( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__130( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__132( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__133(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__132(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__115(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__133(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__126(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__134(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__127(rest, [], stack, context, line, offset) + end + + defp authorization_parser__135( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__136(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__135(rest, acc, stack, context, line, offset) do + authorization_parser__134(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__136( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__138(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__136(rest, acc, stack, context, line, offset) do + authorization_parser__137(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__138(rest, acc, stack, context, line, offset) do + authorization_parser__136(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__137(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__126(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__126(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__139( + rest, + [algorithm: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__139(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__15(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__140(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__116(rest, [], stack, context, line, offset) + end + + defp authorization_parser__141(rest, acc, stack, context, line, offset) do + authorization_parser__142(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__142( + <<"signature", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__143(rest, acc, stack, context, comb__line, comb__offset + 9) + end + + defp authorization_parser__142(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__140(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__143( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__145(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__143(rest, acc, stack, context, line, offset) do + authorization_parser__144(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__145(rest, acc, stack, context, line, offset) do + authorization_parser__143(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__144( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__146(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__144(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__140(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__146( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__148(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__146(rest, acc, stack, context, line, offset) do + authorization_parser__147(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__148(rest, acc, stack, context, line, offset) do + authorization_parser__146(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__147(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__149(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__149(rest, acc, stack, context, line, offset) do + authorization_parser__150(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__150(rest, acc, stack, context, line, offset) do + authorization_parser__160( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__152( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__153(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__152(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__140(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__153(rest, acc, stack, context, line, offset) do + authorization_parser__155( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__155( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__156(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__155( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__156(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__155(rest, acc, stack, context, line, offset) do + authorization_parser__154(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__154(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__157(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__156( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__155( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__157( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__158(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__157(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__140(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__158(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__151(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__159(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__152(rest, [], stack, context, line, offset) + end + + defp authorization_parser__160( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__161(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__160(rest, acc, stack, context, line, offset) do + authorization_parser__159(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__161( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__163(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__161(rest, acc, stack, context, line, offset) do + authorization_parser__162(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__163(rest, acc, stack, context, line, offset) do + authorization_parser__161(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__162(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__151(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__151(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__164( + rest, + [signature: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__164(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__15(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__165(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__141(rest, [], stack, context, line, offset) + end + + defp authorization_parser__166(rest, acc, stack, context, line, offset) do + authorization_parser__167(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__167( + <<"keyId", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__168(rest, acc, stack, context, comb__line, comb__offset + 5) + end + + defp authorization_parser__167(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__165(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__168( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__170(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__168(rest, acc, stack, context, line, offset) do + authorization_parser__169(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__170(rest, acc, stack, context, line, offset) do + authorization_parser__168(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__169( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__171(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__169(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__165(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__171( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__173(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__171(rest, acc, stack, context, line, offset) do + authorization_parser__172(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__173(rest, acc, stack, context, line, offset) do + authorization_parser__171(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__172(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__174(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__174(rest, acc, stack, context, line, offset) do + authorization_parser__175(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__175(rest, acc, stack, context, line, offset) do + authorization_parser__185( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__177( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__178(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__177(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__165(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__178(rest, acc, stack, context, line, offset) do + authorization_parser__180( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__180( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__181(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__180( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__181(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__180(rest, acc, stack, context, line, offset) do + authorization_parser__179(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__179(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__182(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__181( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__180( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__182( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__183(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__182(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__165(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__183(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__176(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__184(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__177(rest, [], stack, context, line, offset) + end + + defp authorization_parser__185( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__186(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__185(rest, acc, stack, context, line, offset) do + authorization_parser__184(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__186( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__188(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__186(rest, acc, stack, context, line, offset) do + authorization_parser__187(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__188(rest, acc, stack, context, line, offset) do + authorization_parser__186(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__187(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__176(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__176(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__189( + rest, + [key_id: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__189(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__15(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__15(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__11(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__11(rest, acc, stack, context, line, offset) do + authorization_parser__191( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__191(rest, acc, stack, context, line, offset) do + authorization_parser__192(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__192( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__194(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__192(rest, acc, stack, context, line, offset) do + authorization_parser__193(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__194(rest, acc, stack, context, line, offset) do + authorization_parser__192(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__193( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 44 do + authorization_parser__195(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__193(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__190(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__195( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__197(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__195(rest, acc, stack, context, line, offset) do + authorization_parser__196(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__197(rest, acc, stack, context, line, offset) do + authorization_parser__195(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__196(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__198(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__198(rest, acc, stack, context, line, offset) do + authorization_parser__202( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__200(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__199(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__201(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__200(rest, [], stack, context, line, offset) + end + + defp authorization_parser__202(rest, acc, stack, context, line, offset) do + authorization_parser__354( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__204(rest, acc, stack, context, line, offset) do + authorization_parser__205(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__205( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__206(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__205(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__201(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__206( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__208(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__206(rest, acc, stack, context, line, offset) do + authorization_parser__207(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__208(rest, acc, stack, context, line, offset) do + authorization_parser__206(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__207( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__210(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__207(rest, acc, stack, context, line, offset) do + authorization_parser__209(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__210(rest, acc, stack, context, line, offset) do + authorization_parser__207(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__209( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__211(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__209(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__201(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__211( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__213(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__211(rest, acc, stack, context, line, offset) do + authorization_parser__212(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__213(rest, acc, stack, context, line, offset) do + authorization_parser__211(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__212(rest, acc, stack, context, line, offset) do + authorization_parser__223( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__215( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__216(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__215(rest, _acc, stack, context, line, offset) do + [_, _, _, _, acc | stack] = stack + authorization_parser__201(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__216(rest, acc, stack, context, line, offset) do + authorization_parser__218( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__218( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__219(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__218( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__219(rest, acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__218(rest, acc, stack, context, line, offset) do + authorization_parser__217(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__217(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__220(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__219( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__218( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__220( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__221(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__220(rest, _acc, stack, context, line, offset) do + [_, _, _, _, acc | stack] = stack + authorization_parser__201(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__221(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__214(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__222(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__215(rest, [], stack, context, line, offset) + end + + defp authorization_parser__223( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__224(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__223(rest, acc, stack, context, line, offset) do + authorization_parser__222(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__224( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__226(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__224(rest, acc, stack, context, line, offset) do + authorization_parser__225(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__226(rest, acc, stack, context, line, offset) do + authorization_parser__224(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__225(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__214(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__214(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__227(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__227(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__203(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__228(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__204(rest, [], stack, context, line, offset) + end + + defp authorization_parser__229(rest, acc, stack, context, line, offset) do + authorization_parser__230(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__230( + <<"headers", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__231(rest, acc, stack, context, comb__line, comb__offset + 7) + end + + defp authorization_parser__230(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__228(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__231( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__233(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__231(rest, acc, stack, context, line, offset) do + authorization_parser__232(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__233(rest, acc, stack, context, line, offset) do + authorization_parser__231(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__232( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__234(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__232(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__228(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__234( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__236(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__234(rest, acc, stack, context, line, offset) do + authorization_parser__235(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__236(rest, acc, stack, context, line, offset) do + authorization_parser__234(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__235(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__237(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__237(rest, acc, stack, context, line, offset) do + authorization_parser__238(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__238(rest, acc, stack, context, line, offset) do + authorization_parser__248( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__240( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__241(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__240(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__228(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__241(rest, acc, stack, context, line, offset) do + authorization_parser__243( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__243( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__244(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__243( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__244(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__243(rest, acc, stack, context, line, offset) do + authorization_parser__242(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__242(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__245(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__244( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__243( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__245( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__246(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__245(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__228(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__246(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__239(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__247(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__240(rest, [], stack, context, line, offset) + end + + defp authorization_parser__248( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__249(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__248(rest, acc, stack, context, line, offset) do + authorization_parser__247(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__249( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__251(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__249(rest, acc, stack, context, line, offset) do + authorization_parser__250(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__251(rest, acc, stack, context, line, offset) do + authorization_parser__249(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__250(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__239(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__239(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__252( + rest, + [headers: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__252(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__203(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__253(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__229(rest, [], stack, context, line, offset) + end + + defp authorization_parser__254(rest, acc, stack, context, line, offset) do + authorization_parser__255(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__255( + <<"expires", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__256(rest, acc, stack, context, comb__line, comb__offset + 7) + end + + defp authorization_parser__255(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__253(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__256( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__258(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__256(rest, acc, stack, context, line, offset) do + authorization_parser__257(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__258(rest, acc, stack, context, line, offset) do + authorization_parser__256(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__257( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__259(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__257(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__253(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__259( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__261(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__259(rest, acc, stack, context, line, offset) do + authorization_parser__260(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__261(rest, acc, stack, context, line, offset) do + authorization_parser__259(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__260(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__262(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__262(rest, acc, stack, context, line, offset) do + authorization_parser__263(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__263(rest, acc, stack, context, line, offset) do + authorization_parser__273( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__265( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__266(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__265(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__253(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__266(rest, acc, stack, context, line, offset) do + authorization_parser__268( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__268( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__269(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__268( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__269(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__268(rest, acc, stack, context, line, offset) do + authorization_parser__267(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__267(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__270(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__269( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__268( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__270( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__271(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__270(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__253(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__271(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__264(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__272(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__265(rest, [], stack, context, line, offset) + end + + defp authorization_parser__273( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__274(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__273(rest, acc, stack, context, line, offset) do + authorization_parser__272(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__274( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__276(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__274(rest, acc, stack, context, line, offset) do + authorization_parser__275(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__276(rest, acc, stack, context, line, offset) do + authorization_parser__274(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__275(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__264(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__264(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__277( + rest, + [expires: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__277(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__203(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__278(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__254(rest, [], stack, context, line, offset) + end + + defp authorization_parser__279(rest, acc, stack, context, line, offset) do + authorization_parser__280(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__280( + <<"created", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__281(rest, acc, stack, context, comb__line, comb__offset + 7) + end + + defp authorization_parser__280(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__278(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__281( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__283(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__281(rest, acc, stack, context, line, offset) do + authorization_parser__282(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__283(rest, acc, stack, context, line, offset) do + authorization_parser__281(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__282( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__284(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__282(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__278(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__284( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__286(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__284(rest, acc, stack, context, line, offset) do + authorization_parser__285(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__286(rest, acc, stack, context, line, offset) do + authorization_parser__284(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__285(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__287(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__287(rest, acc, stack, context, line, offset) do + authorization_parser__288(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__288(rest, acc, stack, context, line, offset) do + authorization_parser__298( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__290( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__291(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__290(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__278(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__291(rest, acc, stack, context, line, offset) do + authorization_parser__293( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__293( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__294(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__293( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__294(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__293(rest, acc, stack, context, line, offset) do + authorization_parser__292(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__292(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__295(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__294( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__293( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__295( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__296(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__295(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__278(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__296(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__289(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__297(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__290(rest, [], stack, context, line, offset) + end + + defp authorization_parser__298( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__299(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__298(rest, acc, stack, context, line, offset) do + authorization_parser__297(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__299( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__301(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__299(rest, acc, stack, context, line, offset) do + authorization_parser__300(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__301(rest, acc, stack, context, line, offset) do + authorization_parser__299(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__300(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__289(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__289(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__302( + rest, + [created: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__302(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__203(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__303(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__279(rest, [], stack, context, line, offset) + end + + defp authorization_parser__304(rest, acc, stack, context, line, offset) do + authorization_parser__305(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__305( + <<"algorithm", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__306(rest, acc, stack, context, comb__line, comb__offset + 9) + end + + defp authorization_parser__305(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__303(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__306( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__308(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__306(rest, acc, stack, context, line, offset) do + authorization_parser__307(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__308(rest, acc, stack, context, line, offset) do + authorization_parser__306(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__307( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__309(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__307(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__303(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__309( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__311(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__309(rest, acc, stack, context, line, offset) do + authorization_parser__310(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__311(rest, acc, stack, context, line, offset) do + authorization_parser__309(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__310(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__312(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__312(rest, acc, stack, context, line, offset) do + authorization_parser__313(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__313(rest, acc, stack, context, line, offset) do + authorization_parser__323( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__315( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__316(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__315(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__303(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__316(rest, acc, stack, context, line, offset) do + authorization_parser__318( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__318( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__319(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__318( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__319(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__318(rest, acc, stack, context, line, offset) do + authorization_parser__317(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__317(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__320(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__319( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__318( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__320( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__321(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__320(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__303(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__321(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__314(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__322(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__315(rest, [], stack, context, line, offset) + end + + defp authorization_parser__323( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__324(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__323(rest, acc, stack, context, line, offset) do + authorization_parser__322(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__324( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__326(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__324(rest, acc, stack, context, line, offset) do + authorization_parser__325(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__326(rest, acc, stack, context, line, offset) do + authorization_parser__324(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__325(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__314(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__314(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__327( + rest, + [algorithm: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__327(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__203(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__328(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__304(rest, [], stack, context, line, offset) + end + + defp authorization_parser__329(rest, acc, stack, context, line, offset) do + authorization_parser__330(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__330( + <<"signature", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__331(rest, acc, stack, context, comb__line, comb__offset + 9) + end + + defp authorization_parser__330(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__328(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__331( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__333(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__331(rest, acc, stack, context, line, offset) do + authorization_parser__332(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__333(rest, acc, stack, context, line, offset) do + authorization_parser__331(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__332( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__334(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__332(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__328(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__334( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__336(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__334(rest, acc, stack, context, line, offset) do + authorization_parser__335(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__336(rest, acc, stack, context, line, offset) do + authorization_parser__334(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__335(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__337(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__337(rest, acc, stack, context, line, offset) do + authorization_parser__338(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__338(rest, acc, stack, context, line, offset) do + authorization_parser__348( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__340( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__341(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__340(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__328(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__341(rest, acc, stack, context, line, offset) do + authorization_parser__343( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__343( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__344(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__343( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__344(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__343(rest, acc, stack, context, line, offset) do + authorization_parser__342(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__342(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__345(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__344( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__343( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__345( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__346(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__345(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__328(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__346(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__339(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__347(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__340(rest, [], stack, context, line, offset) + end + + defp authorization_parser__348( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__349(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__348(rest, acc, stack, context, line, offset) do + authorization_parser__347(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__349( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__351(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__349(rest, acc, stack, context, line, offset) do + authorization_parser__350(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__351(rest, acc, stack, context, line, offset) do + authorization_parser__349(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__350(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__339(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__339(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__352( + rest, + [signature: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__352(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__203(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__353(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__329(rest, [], stack, context, line, offset) + end + + defp authorization_parser__354(rest, acc, stack, context, line, offset) do + authorization_parser__355(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__355( + <<"keyId", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + authorization_parser__356(rest, acc, stack, context, comb__line, comb__offset + 5) + end + + defp authorization_parser__355(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__353(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__356( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__358(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__356(rest, acc, stack, context, line, offset) do + authorization_parser__357(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__358(rest, acc, stack, context, line, offset) do + authorization_parser__356(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__357( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 61 do + authorization_parser__359(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__357(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + authorization_parser__353(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__359( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 32 or x0 === 9 do + authorization_parser__361(rest, acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__359(rest, acc, stack, context, line, offset) do + authorization_parser__360(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__361(rest, acc, stack, context, line, offset) do + authorization_parser__359(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__360(rest, _user_acc, [acc | stack], context, line, offset) do + authorization_parser__362(rest, [] ++ acc, stack, context, line, offset) + end + + defp authorization_parser__362(rest, acc, stack, context, line, offset) do + authorization_parser__363(rest, [], [acc | stack], context, line, offset) + end + + defp authorization_parser__363(rest, acc, stack, context, line, offset) do + authorization_parser__373( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp authorization_parser__365( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__366(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__365(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__353(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__366(rest, acc, stack, context, line, offset) do + authorization_parser__368( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp authorization_parser__368( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 9 or x0 === 32 or x0 === 33 or (x0 >= 35 and x0 <= 91) or + (x0 >= 93 and x0 <= 126) or (x0 >= 128 and x0 <= 255) do + authorization_parser__369(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__368( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 92 and + (x1 === 9 or x1 === 32 or (x1 >= 33 and x1 <= 126) or (x1 >= 128 and x1 <= 255)) do + authorization_parser__369(rest, [x1, x0] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp authorization_parser__368(rest, acc, stack, context, line, offset) do + authorization_parser__367(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__367(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__370(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__369( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__368( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__370( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 34 do + authorization_parser__371(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__370(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + authorization_parser__353(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__371(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__364(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__372(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + authorization_parser__365(rest, [], stack, context, line, offset) + end + + defp authorization_parser__373( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__374(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__373(rest, acc, stack, context, line, offset) do + authorization_parser__372(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__374( + <>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when x0 === 33 or x0 === 35 or x0 === 36 or x0 === 37 or x0 === 38 or x0 === 39 or + x0 === 42 or x0 === 43 or x0 === 45 or x0 === 46 or x0 === 94 or x0 === 95 or + x0 === 96 or x0 === 124 or x0 === 126 or (x0 >= 48 and x0 <= 57) or + (x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90) do + authorization_parser__376(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp authorization_parser__374(rest, acc, stack, context, line, offset) do + authorization_parser__375(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__376(rest, acc, stack, context, line, offset) do + authorization_parser__374(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__375(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__364(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__364(rest, user_acc, [acc | stack], context, line, offset) do + authorization_parser__377( + rest, + [key_id: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp authorization_parser__377(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__203(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__203(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__199(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__190(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + authorization_parser__378(rest, acc, stack, context, line, offset) + end + + defp authorization_parser__199( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + authorization_parser__191( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp authorization_parser__378(rest, acc, [_, previous_acc | stack], context, line, offset) do + authorization_parser__2(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp authorization_parser__2(rest, acc, _stack, context, line, offset) do + {:ok, acc, rest, context, line, offset} + end + + def authorization(input) do + with {:ok, result, "", _, _, _} <- authorization_parser(input), + false <- duplicate_keys?(result) do + {:ok, Enum.map(result, &unescape/1)} + else + _ -> + {:error, "malformed authorization header"} + end + end + + defp duplicate_keys?(keyword_list) do + keys = Keyword.keys(keyword_list) + unique_keys = Enum.uniq(keys) + length(keys) != length(unique_keys) + end + + defp unescape({key, [?" | rest]}) do + {key, to_string(unescape_value(rest))} + end + + defp unescape({key, value}) do + {key, to_string(value)} + end + + defp unescape_value(value, acc \\ []) + + defp unescape_value([?"], acc), do: Enum.reverse(acc) + + defp unescape_value([?\\, c | rest], acc) do + unescape_value(rest, [c | acc]) + end + + defp unescape_value([c | rest], acc) do + unescape_value(rest, [c | acc]) + end +end diff --git a/lib/plug_signature/parser.ex.exs b/lib/plug_signature/parser.ex.exs new file mode 100644 index 0000000..5db803b --- /dev/null +++ b/lib/plug_signature/parser.ex.exs @@ -0,0 +1,164 @@ +defmodule PlugSignature.Parser do + @moduledoc false + + # credo:disable-for-this-file + + # parsec:PlugSignature.Parser + + import NimbleParsec + + defmodule Helpers do + @moduledoc false + + import NimbleParsec + + # rfc7230 + + ### section 3.2.3 + # RWS = 1*( SP / HTAB ) + # ; required whitespace + # OWS = *( SP / HTAB ) + # ; optional whitespace + # BWS = OWS + # ; "bad" whitespace + def rws(combinator \\ empty()), do: times(combinator, ascii_char([?\s, ?\t]), min: 1) + def ows(combinator \\ empty()), do: repeat(combinator, ascii_char([?\s, ?\t])) + def bws(combinator \\ empty()), do: ows(combinator) + + ### section 3.2.6 + # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + # / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + # / DIGIT / ALPHA + # ; any VCHAR, except delimiters + # token = 1*tchar + # obs-text = %x80-FF + # qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text + # quoted-pair = quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + # quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + def tchar(combinator \\ empty()) do + ascii_char( + combinator, + [?!, ?#, ?$, ?%, ?&, ?', ?*, ?+, ?-, ?., ?^, ?_, ?`, ?|, ?~] ++ + [?0..?9, ?a..?z, ?A..?Z] + ) + end + + def token(combinator \\ empty()), do: times(combinator, tchar(), min: 1) + + def qdtext(combinator \\ empty()) do + ascii_char(combinator, [?\t, ?\s, 0x21, 0x23..0x5B, 0x5D..0x7E, 0x80..0xFF]) + end + + def quoted_pair(combinator \\ empty()) do + combinator + |> ascii_char([?\\]) + |> ascii_char([?\t, ?\s, 0x21..0x7E, 0x80..0xFF]) + end + + def quoted_string(combinator \\ empty()) do + combinator + |> ascii_char([?"]) + |> repeat(choice([qdtext(), quoted_pair()])) + |> ascii_char([?"]) + end + + ### Based on RFC7235 Appendix C + # auth-param = token BWS "=" BWS ( token / quoted-string ) + def equals(combinator \\ empty()) do + combinator + |> bws() + |> ascii_char([?=]) + |> bws() + end + + def comma(combinator \\ empty()) do + combinator + |> bws() + |> ascii_char([?,]) + |> bws() + end + + def generic_param(combinator \\ empty()) do + combinator + |> token() + |> equals() + |> choice([token(), quoted_string()]) + end + + def named_param(combinator \\ empty(), name, tag) do + combinator + |> ignore( + string(name) + |> equals() + ) + |> tag(choice([token(), quoted_string()]), tag) + end + + def signature_param(combinator \\ empty()) do + choice(combinator, [ + named_param("keyId", :key_id), + named_param("signature", :signature), + named_param("algorithm", :algorithm), + named_param("created", :created), + named_param("expires", :expires), + named_param("headers", :headers), + ignore(generic_param()) + ]) + end + + def signature_params(combinator \\ empty()) do + combinator + |> optional(signature_param()) + |> repeat(ignore(comma()) |> optional(signature_param())) + end + + def authorization(combinator \\ empty()) do + combinator + |> ignore(string("Signature")) + |> optional( + ignore(times(ascii_char([?\s]), min: 1)) + |> signature_params() + ) + end + end + + defparsecp(:authorization_parser, Helpers.authorization()) + + # parsec:PlugSignature.Parser + + def authorization(input) do + with {:ok, result, "", _, _, _} <- authorization_parser(input), + false <- duplicate_keys?(result) do + {:ok, Enum.map(result, &unescape/1)} + else + _ -> + {:error, "malformed authorization header"} + end + end + + defp duplicate_keys?(keyword_list) do + keys = Keyword.keys(keyword_list) + unique_keys = Enum.uniq(keys) + length(keys) != length(unique_keys) + end + + defp unescape({key, [?" | rest]}) do + {key, to_string(unescape_value(rest))} + end + + defp unescape({key, value}) do + {key, to_string(value)} + end + + defp unescape_value(value, acc \\ []) + + defp unescape_value([?"], acc), do: Enum.reverse(acc) + + defp unescape_value([?\\, c | rest], acc) do + unescape_value(rest, [c | acc]) + end + + defp unescape_value([c | rest], acc) do + unescape_value(rest, [c | acc]) + end +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..4921dde --- /dev/null +++ b/mix.exs @@ -0,0 +1,57 @@ +defmodule PlugSignature.MixProject do + use Mix.Project + + @version "0.5.0" + + def project do + [ + app: :plug_signature, + version: @version, + elixir: "~> 1.6", + start_permanent: Mix.env() == :prod, + deps: deps(), + description: description(), + package: package(), + docs: docs(), + source_url: "https://github.com/voltone/plug_signature" + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger, :crypto, :public_key] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:plug, "~> 1.5"}, + {:nimble_parsec, "~> 0.5", only: :dev}, + {:ex_doc, "~> 0.21", only: :dev}, + {:credo, "~> 1.1", only: :dev}, + {:x509, "~> 0.5", only: :test} + ] + end + + defp description do + "Server side implementation of IETF HTTP signature draft as a reusable Plug" + end + + defp package do + [ + maintainers: ["Bram Verburg"], + licenses: ["BSD-3-Clause"], + links: %{"GitHub" => "https://github.com/voltone/plug_signature"} + ] + end + + defp docs do + [ + main: "readme", + extras: ["README.md"], + source_ref: "v#{@version}" + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..1270cf4 --- /dev/null +++ b/mix.lock @@ -0,0 +1,14 @@ +%{ + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, + "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, + "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"}, + "plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, + "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, + "x509": {:hex, :x509, "0.8.0", "b286b9dbb32801f76f48bdea476304d280c64ce172ea330c23d8df7ea9e75ce6", [:mix], [], "hexpm"}, +} diff --git a/plug_signature_example/README.md b/plug_signature_example/README.md new file mode 100644 index 0000000..f7fdef1 --- /dev/null +++ b/plug_signature_example/README.md @@ -0,0 +1,67 @@ +# PlugSignatureExample + +Application demonstrating the use of `PlugSignature` and `PlugBodyDigest` for +HTTP request authentication. + +## Starting the application + +Before starting the application for the first time, ensure the necessary +dependencies have been installed using `mix deps.get`. + +Run the application using `mix run --no-halt` or `iex -S mix`. The server +listens on port 4040. + +## OpenSSL/cURL client + +A bash script called `request.sh` may be used to trigger a number of +scenarios that demonstrate the functionality provided by `PlugSignature` and +`PlugBodyDigest`. The script is invoked as `request.sh [ []]`. + +Scenarios: + + * `valid` (default) - make a valid request, signed using ECDSA + * `expired` - make a request with a 10 minute old signature, resulting in a + 401 response + * `digest` - make a request with an checksum in the Digest header that does + not match the request body, resulting in a 403 response + * `key` - make a request with an unknown keyId, resulting in a 401 response + * `headers` - make a request with a signature that does not cover the + minimum set of required headers, resulting in a 401 response + * `signature` - make a request with an invalid sigature value, resulting in + a 401 response + * `rsa` - make a valid request, signed using RSASSA-PSS + * `hmac` - make a valid request, using HMAC authentication + +The optional second argument selects the request method: `post` (default) or +`get`. + +The output shows the HTTP request headers, including the Digest and +Authorization headers, as well as response headers and body. + +## Tesla client + +To use the Tesla client, start an IEx session with `iex -S mix`, the create a +client and send requests to the server as follows: + +```elixir +iex> client = PlugSignatureExample.Client.new("ec.pub", "priv/ec.key") +%Tesla.Client{ + # ... +} +iex> PlugSignatureExample.Client.get(client, %{test: 123}) +{:ok, + %Tesla.Env{ + # ... + status: 200, + # ... + } +} +iex> PlugSignatureExample.Client.post(client, %{test: 123}) +{:ok, + %Tesla.Env{ + # ... + status: 200, + # ... + } +} +``` diff --git a/plug_signature_example/lib/plug_signature_example.ex b/plug_signature_example/lib/plug_signature_example.ex new file mode 100644 index 0000000..518b3cd --- /dev/null +++ b/plug_signature_example/lib/plug_signature_example.ex @@ -0,0 +1,25 @@ +defmodule PlugSignatureExample do + use Plug.Builder + import Plug.Conn + + plug Plug.Parsers, + parsers: [:urlencoded], + body_reader: {PlugBodyDigest, :digest_body_reader, []} + + plug Plug.Logger + + plug PlugBodyDigest + + plug PlugSignature, + callback_module: PlugSignatureExample.Authentication, + on_success: {PlugSignature, :assign_client, [:client]}, + headers: "(request-target) (created) host digest" + + plug :echo + + def echo(conn, _opts) do + client = conn.assigns[:client] + params = conn.params + send_resp(conn, 200, "Client: #{client}\nParams: #{inspect(params)}\n\n") + end +end diff --git a/plug_signature_example/lib/plug_signature_example/application.ex b/plug_signature_example/lib/plug_signature_example/application.ex new file mode 100644 index 0000000..a885ca2 --- /dev/null +++ b/plug_signature_example/lib/plug_signature_example/application.ex @@ -0,0 +1,19 @@ +defmodule PlugSignatureExample.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + def start(_type, _args) do + # List all child processes to be supervised + children = [ + {Plug.Cowboy, scheme: :http, plug: PlugSignatureExample, options: [port: 4040]} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: PlugSignatureExample.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/plug_signature_example/lib/plug_signature_example/authentication.ex b/plug_signature_example/lib/plug_signature_example/authentication.ex new file mode 100644 index 0000000..33396bd --- /dev/null +++ b/plug_signature_example/lib/plug_signature_example/authentication.ex @@ -0,0 +1,24 @@ +defmodule PlugSignatureExample.Authentication do + @moduledoc """ + Wrapper around PlugSignature + """ + + @behaviour PlugSignature.Callback + + @impl true + def client_lookup("hmac.key", _algorithm, _conn) do + {:ok, "Some client", File.read!("priv/hmac.key")} + end + + def client_lookup(key_id, _algorithm, _conn) do + path = Application.app_dir(:plug_signature_example, "priv") + + with {:ok, pem} <- File.read(Path.join(path, key_id)), + {:ok, public_key} <- X509.PublicKey.from_pem(pem) do + {:ok, "Some client", public_key} + else + _error -> + {:error, "Key not found"} + end + end +end diff --git a/plug_signature_example/lib/plug_signature_example/client.ex b/plug_signature_example/lib/plug_signature_example/client.ex new file mode 100644 index 0000000..9069b9b --- /dev/null +++ b/plug_signature_example/lib/plug_signature_example/client.ex @@ -0,0 +1,26 @@ +defmodule PlugSignatureExample.Client do + def new(key_id, key_path) do + private_key = + key_path + |> File.read!() + |> X509.PrivateKey.from_pem!() + + Tesla.client([ + {Tesla.Middleware.BaseUrl, "http://localhost:4040"}, + # Ensure body is encoded before invoking HttpSignatureAuth, so a + # digest can be calculated + Tesla.Middleware.FormUrlencoded, + {PlugSignatureExample.Middleware.HttpSignatureAuth, + key_id: key_id, private_key: private_key, headers: "(request-target) (created) host digest"}, + Tesla.Middleware.Logger + ]) + end + + def get(client, params) do + Tesla.get(client, "/", query: params) + end + + def post(client, params) do + Tesla.post(client, "/", params) + end +end diff --git a/plug_signature_example/lib/plug_signature_example/middleware/http_signature_auth.ex b/plug_signature_example/lib/plug_signature_example/middleware/http_signature_auth.ex new file mode 100644 index 0000000..93458c0 --- /dev/null +++ b/plug_signature_example/lib/plug_signature_example/middleware/http_signature_auth.ex @@ -0,0 +1,82 @@ +defmodule PlugSignatureExample.Middleware.HttpSignatureAuth do + @moduledoc """ + Tesla middleware for HTTP request signing according to + https://tools.ietf.org/html/draft-cavage-http-signatures-11 + + Supports the hs2019 algorithm only. + """ + + alias PlugSignature.Crypto + + @behaviour Tesla.Middleware + + def call(env, next, opts) do + key_id = opts[:key_id] + private_key = opts[:private_key] + headers = Keyword.get(opts, :headers, "(created)") + + env + |> with_digest() + |> signed(key_id, private_key, headers) + |> Tesla.run(next) + end + + defp with_digest(%{body: body} = env) do + Tesla.put_header(env, "digest", "SHA-256=#{digest(body)}") + end + + defp digest(nil), do: :crypto.hash(:sha256, "") |> Base.encode64() + defp digest(body), do: :crypto.hash(:sha256, body) |> Base.encode64() + + defp signed(env, key_id, private_key, headers) do + created = + DateTime.utc_now() + |> DateTime.to_unix() + + to_sign = build_to_sign(headers, env, created) + + signature = + to_sign + |> Crypto.sign!("hs2019", private_key) + |> Base.encode64() + + authorization = + ~s[Signature keyId="#{key_id}",signature="#{signature}",algorithm="hs2019",created=#{ + created + },headers="#{headers}"] + + Tesla.put_header(env, "authorization", authorization) + end + + defp build_to_sign(headers, env, created) do + url = URI.parse(env.url) + path = url.path + host = url.authority + + query = + case {url.query, URI.encode_query(env.query)} do + {nil, ""} -> "" + {from_url, ""} when byte_size(from_url) > 0 -> "?" <> from_url + {nil, from_env} when byte_size(from_env) > 0 -> "?" <> from_env + end + + request_target = "#{env.method |> to_string() |> String.downcase()} #{path}#{query}" + + headers + |> String.split() + |> Enum.map(fn + "(request-target)" -> + "(request-target): #{request_target}" + + "(created)" -> + "(created): #{created}" + + "host" -> + "host: #{host}" + + header -> + "#{header}: #{Tesla.get_header(env, header)}" + end) + |> Enum.join("\n") + end +end diff --git a/plug_signature_example/mix.exs b/plug_signature_example/mix.exs new file mode 100644 index 0000000..b04c078 --- /dev/null +++ b/plug_signature_example/mix.exs @@ -0,0 +1,32 @@ +defmodule PlugSignatureExample.MixProject do + use Mix.Project + + def project do + [ + app: :plug_signature_example, + version: "0.1.0", + elixir: "~> 1.8", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {PlugSignatureExample.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:plug_signature, path: ".."}, + {:plug_body_digest, "~> 0.5"}, + {:plug_cowboy, "~> 2.1"}, + {:x509, "~> 0.5"}, + {:tesla, "~> 1.3"} + ] + end +end diff --git a/plug_signature_example/mix.lock b/plug_signature_example/mix.lock new file mode 100644 index 0000000..4def221 --- /dev/null +++ b/plug_signature_example/mix.lock @@ -0,0 +1,12 @@ +%{ + "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, + "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"}, + "plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, + "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, + "tesla": {:hex, :tesla, "1.3.0", "f35d72f029e608f9cdc6f6d6fcc7c66cf6d6512a70cfef9206b21b8bd0203a30", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 0.4", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, + "x509": {:hex, :x509, "0.8.0", "b286b9dbb32801f76f48bdea476304d280c64ce172ea330c23d8df7ea9e75ce6", [:mix], [], "hexpm"}, +} diff --git a/plug_signature_example/priv/ec.key b/plug_signature_example/priv/ec.key new file mode 100644 index 0000000..004b101 --- /dev/null +++ b/plug_signature_example/priv/ec.key @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJ/DxtD0tOZaEZD78/Brlk7gHzb+DFpD/cQXvyJdWPVsoAoGCCqGSM49 +AwEHoUQDQgAEyOsVbBzt8/ueHICrEXxN2+OiDUL8VTf1VUwJ8NhubqhVwbGT+Yst +pEGLkUdqo4bIRSOXgMaK0/+a5JiQVsnLZQ== +-----END EC PRIVATE KEY----- + diff --git a/plug_signature_example/priv/ec.pem b/plug_signature_example/priv/ec.pem new file mode 100644 index 0000000..16adeb3 --- /dev/null +++ b/plug_signature_example/priv/ec.pem @@ -0,0 +1,11 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJ/DxtD0tOZaEZD78/Brlk7gHzb+DFpD/cQXvyJdWPVsoAoGCCqGSM49 +AwEHoUQDQgAEyOsVbBzt8/ueHICrEXxN2+OiDUL8VTf1VUwJ8NhubqhVwbGT+Yst +pEGLkUdqo4bIRSOXgMaK0/+a5JiQVsnLZQ== +-----END EC PRIVATE KEY----- + +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyOsVbBzt8/ueHICrEXxN2+OiDUL8 +VTf1VUwJ8NhubqhVwbGT+YstpEGLkUdqo4bIRSOXgMaK0/+a5JiQVsnLZQ== +-----END PUBLIC KEY----- + diff --git a/plug_signature_example/priv/ec.pub b/plug_signature_example/priv/ec.pub new file mode 100644 index 0000000..cfc752e --- /dev/null +++ b/plug_signature_example/priv/ec.pub @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyOsVbBzt8/ueHICrEXxN2+OiDUL8 +VTf1VUwJ8NhubqhVwbGT+YstpEGLkUdqo4bIRSOXgMaK0/+a5JiQVsnLZQ== +-----END PUBLIC KEY----- + diff --git a/plug_signature_example/priv/hmac.key b/plug_signature_example/priv/hmac.key new file mode 100644 index 0000000..69da777 --- /dev/null +++ b/plug_signature_example/priv/hmac.key @@ -0,0 +1 @@ +supersecrethmackey \ No newline at end of file diff --git a/plug_signature_example/priv/rsa.key b/plug_signature_example/priv/rsa.key new file mode 100644 index 0000000..5e50ee2 --- /dev/null +++ b/plug_signature_example/priv/rsa.key @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAuIVptrhDcMlpWm3MmPwdakIA9mkTNi8PyrklorqMc3SDhbI/ +rkgOLtRJr588XnT8Yb35fgallF9MpBzTrg2vV636wmGygzAjjPdqaThdVWlIx4MZ +UBvdrsuPG6mh0ZCM3pBDiWdGKxmfiKHfSe6qbciHOSJYu1J1HIN7mDmuyC2g5JVs +vEp/Hyy4MnRCZcQf0TjB5bUTHtwrS6l4Z50534umxfDUynmRFYJKfDz7Us8FFbxj +ShHgC8E4fcSdiEN75vZejoRPLxHMLCD8nbvRcAMOvNXJOLz4Qbx2yzPIr5JLHEG/ +aRLpkoTD9YW3pXlokV2S5uzMR87b1cGuOGCOiQIDAQABAoIBAQCfZTglzFUNyB9H +K5RTD27FjJDSS4B6DPtiTr/xK58KWTsIMiuKfNorn9yrZi27Fumx8W7lbA569jv5 +hKFjOJUgc70rT0PqyZncOxpkHHmbv6BMILasGfZM+bD833NW2bymwg5lUp4tuyux +1stRTWdSAKi3NTFbV+aso/QPUrzmU/MMTyneJi7DhtEGH+JywAcAl2u+1ycX1i/E +XrYHH9B1r0zeO/A4ItPTRAoaZOXrJAE7+xjzB5YB+6rzCrmxbtBdZXyPFiYoqZui +dkqqUpq3pxHKS8+0mflNfZTW8WEFgZBxlsXBMYYUDw5iYLgm2F9xYd7iulcLTUow +dOtNdw9RAoGBAO505g3Acn/dYYzUHV7zzVJULEEIpN6lbxcSEz9LfOZM7LN6kDDY +MpMw57mbUG7YYoYYvmLASDvz7G5Okifb8C0Abz4HY5V9O4NnYCS0h8rcEl6mGVET +u1AMoDVttsG2FBJp95HnlVLdpz95eJjrbynLZq/Sa6x+CRlaHNkI20KtAoGBAMYY +sPLJQI21SR3A1PwBY3PEHRMg8UQoCy0u+L/meWtdaG1Nf8kRC1faEXtiSGkLUNoP +WrA7ku4UDWZukaNwWnHZe5Uw564cP4a7dN/ZQx/N+Jg+NuaKhCWVvEjxCuA1ZsVe +U1noJELiqIS8YA8RuhaTD0N3gtIoRTCiQZKuDBLNAoGBAJCmEuOmqQ5NcZ5nEYYG +6LcXXlz47GIvAouBKHHNze86HJ/nKk6m508IbJjX0VvcIS/tFJh8wZS0q+hh+yD4 +tuHlkJWVD+CfvhlA/T5m0LTK+M23fkYDbS3q6sheTG2HkPd2lnpIe/lvgcPsYK6K +qr00qI7hWvWg4s4hLrytNaxlAoGBALeDGClSFuMwFdPiV2w9PQx5mRWnZtpk3jW1 +VeswbzrvBVZ8fOyfRYrVEWzj14C4YuYfYzvvdGXpXaCOvYxTAPaHKt1CuN2qfY8r +CVJ1yqEkBi/DMsjPeSv4Uryf0Bt0XQhqIX0geLcdkk+k0rgjC+jtwy4VALP/allr +dqOTaMvhAoGASJOYRLWR0D6aTRNT+An2KIhroP2a3B77Pfm6Z8C3h0hHUyj/9HXL +jHK8BzN8Z46E/OC331wQ370Zg2xMG+KhXCP4X9NL41aGJZFUZIhq17dv5epf1e0s +owASpkiBqL811yHEC98ZHDO1fJdut++4P3MZRWNO4rlQz880j69VnDk= +-----END RSA PRIVATE KEY----- + diff --git a/plug_signature_example/priv/rsa.pem b/plug_signature_example/priv/rsa.pem new file mode 100644 index 0000000..27b1305 --- /dev/null +++ b/plug_signature_example/priv/rsa.pem @@ -0,0 +1,38 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAuIVptrhDcMlpWm3MmPwdakIA9mkTNi8PyrklorqMc3SDhbI/ +rkgOLtRJr588XnT8Yb35fgallF9MpBzTrg2vV636wmGygzAjjPdqaThdVWlIx4MZ +UBvdrsuPG6mh0ZCM3pBDiWdGKxmfiKHfSe6qbciHOSJYu1J1HIN7mDmuyC2g5JVs +vEp/Hyy4MnRCZcQf0TjB5bUTHtwrS6l4Z50534umxfDUynmRFYJKfDz7Us8FFbxj +ShHgC8E4fcSdiEN75vZejoRPLxHMLCD8nbvRcAMOvNXJOLz4Qbx2yzPIr5JLHEG/ +aRLpkoTD9YW3pXlokV2S5uzMR87b1cGuOGCOiQIDAQABAoIBAQCfZTglzFUNyB9H +K5RTD27FjJDSS4B6DPtiTr/xK58KWTsIMiuKfNorn9yrZi27Fumx8W7lbA569jv5 +hKFjOJUgc70rT0PqyZncOxpkHHmbv6BMILasGfZM+bD833NW2bymwg5lUp4tuyux +1stRTWdSAKi3NTFbV+aso/QPUrzmU/MMTyneJi7DhtEGH+JywAcAl2u+1ycX1i/E +XrYHH9B1r0zeO/A4ItPTRAoaZOXrJAE7+xjzB5YB+6rzCrmxbtBdZXyPFiYoqZui +dkqqUpq3pxHKS8+0mflNfZTW8WEFgZBxlsXBMYYUDw5iYLgm2F9xYd7iulcLTUow +dOtNdw9RAoGBAO505g3Acn/dYYzUHV7zzVJULEEIpN6lbxcSEz9LfOZM7LN6kDDY +MpMw57mbUG7YYoYYvmLASDvz7G5Okifb8C0Abz4HY5V9O4NnYCS0h8rcEl6mGVET +u1AMoDVttsG2FBJp95HnlVLdpz95eJjrbynLZq/Sa6x+CRlaHNkI20KtAoGBAMYY +sPLJQI21SR3A1PwBY3PEHRMg8UQoCy0u+L/meWtdaG1Nf8kRC1faEXtiSGkLUNoP +WrA7ku4UDWZukaNwWnHZe5Uw564cP4a7dN/ZQx/N+Jg+NuaKhCWVvEjxCuA1ZsVe +U1noJELiqIS8YA8RuhaTD0N3gtIoRTCiQZKuDBLNAoGBAJCmEuOmqQ5NcZ5nEYYG +6LcXXlz47GIvAouBKHHNze86HJ/nKk6m508IbJjX0VvcIS/tFJh8wZS0q+hh+yD4 +tuHlkJWVD+CfvhlA/T5m0LTK+M23fkYDbS3q6sheTG2HkPd2lnpIe/lvgcPsYK6K +qr00qI7hWvWg4s4hLrytNaxlAoGBALeDGClSFuMwFdPiV2w9PQx5mRWnZtpk3jW1 +VeswbzrvBVZ8fOyfRYrVEWzj14C4YuYfYzvvdGXpXaCOvYxTAPaHKt1CuN2qfY8r +CVJ1yqEkBi/DMsjPeSv4Uryf0Bt0XQhqIX0geLcdkk+k0rgjC+jtwy4VALP/allr +dqOTaMvhAoGASJOYRLWR0D6aTRNT+An2KIhroP2a3B77Pfm6Z8C3h0hHUyj/9HXL +jHK8BzN8Z46E/OC331wQ370Zg2xMG+KhXCP4X9NL41aGJZFUZIhq17dv5epf1e0s +owASpkiBqL811yHEC98ZHDO1fJdut++4P3MZRWNO4rlQz880j69VnDk= +-----END RSA PRIVATE KEY----- + +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuIVptrhDcMlpWm3MmPwd +akIA9mkTNi8PyrklorqMc3SDhbI/rkgOLtRJr588XnT8Yb35fgallF9MpBzTrg2v +V636wmGygzAjjPdqaThdVWlIx4MZUBvdrsuPG6mh0ZCM3pBDiWdGKxmfiKHfSe6q +bciHOSJYu1J1HIN7mDmuyC2g5JVsvEp/Hyy4MnRCZcQf0TjB5bUTHtwrS6l4Z505 +34umxfDUynmRFYJKfDz7Us8FFbxjShHgC8E4fcSdiEN75vZejoRPLxHMLCD8nbvR +cAMOvNXJOLz4Qbx2yzPIr5JLHEG/aRLpkoTD9YW3pXlokV2S5uzMR87b1cGuOGCO +iQIDAQAB +-----END PUBLIC KEY----- + diff --git a/plug_signature_example/priv/rsa.pub b/plug_signature_example/priv/rsa.pub new file mode 100644 index 0000000..7179c10 --- /dev/null +++ b/plug_signature_example/priv/rsa.pub @@ -0,0 +1,10 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuIVptrhDcMlpWm3MmPwd +akIA9mkTNi8PyrklorqMc3SDhbI/rkgOLtRJr588XnT8Yb35fgallF9MpBzTrg2v +V636wmGygzAjjPdqaThdVWlIx4MZUBvdrsuPG6mh0ZCM3pBDiWdGKxmfiKHfSe6q +bciHOSJYu1J1HIN7mDmuyC2g5JVsvEp/Hyy4MnRCZcQf0TjB5bUTHtwrS6l4Z505 +34umxfDUynmRFYJKfDz7Us8FFbxjShHgC8E4fcSdiEN75vZejoRPLxHMLCD8nbvR +cAMOvNXJOLz4Qbx2yzPIr5JLHEG/aRLpkoTD9YW3pXlokV2S5uzMR87b1cGuOGCO +iQIDAQAB +-----END PUBLIC KEY----- + diff --git a/plug_signature_example/request.sh b/plug_signature_example/request.sh new file mode 100755 index 0000000..90c8548 --- /dev/null +++ b/plug_signature_example/request.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +# base64 output differs between Linux and OS X +case $(uname) in +Darwin) + base64_args="" + ;; +Linux) + base64_args="-w0" + ;; +esac + +usage () { + cat << EOF +Usage: $0 [ []] + + Scenarios: valid | expired | digest | key | headers | signature | rsa | hmac + Methods: post | get + +EOF + exit 1 +} + +scenario=${1:-valid} +method=${2:-post} + +host=localhost:4040 +params="query=test" +headers="(request-target) (created) host digest" +private_key=priv/ec.key +key_id=ec.pub + +case ${method,,} in +post) + request="/" + body="${params}" + ;; +get) + request="/?${params}" + body="" + ;; +*) + usage + ;; +esac + +body_sha256=$(echo -ne "${body}" | openssl dgst -sha256 -binary | base64 ${base64_args}) +digest="sha-256=${body_sha256}" + +created=$(date +%s) + +case ${scenario,,} in +valid) + ;; +expired) + created=$(date -v -10M +%s) + ;; +digest) + digest="sha-256=v7s8tN6onk17uf3xx9VzMFXenFUkD5G7Yog1laUcjuA=" + ;; +key) + key_id=unknown.pub + ;; +headers) + headers="(request-target) (created) host" + override_signature=$(echo -ne "(request-target): ${method,,} ${request}\n(created): ${created}\nhost: ${host}" | \ + openssl dgst -sha512 -sign ${private_key} | \ + base64 ${base64_args}) + ;; +signature) + override_signature=$(echo -ne "(request-target): ${method,,} /invalid\n(created): ${created}\nhost: ${host}\ndigest: ${digest}" | \ + openssl dgst -sha512 -sign ${private_key} | \ + base64 ${base64_args}) + ;; +rsa) + private_key=priv/rsa.key + override_signature=$(echo -ne "(request-target): ${method,,} ${request}\n(created): ${created}\nhost: ${host}\ndigest: ${digest}" | \ + openssl dgst -sha512 -sigopt rsa_padding_mode:pss -sign ${private_key} | \ + base64 ${base64_args}) + key_id=rsa.pub + ;; +hmac) + hmac_secret=$(cat priv/hmac.key) + override_signature=$(echo -ne "(request-target): ${method,,} ${request}\n(created): ${created}\nhost: ${host}\ndigest: ${digest}" | \ + openssl dgst -hmac ${hmac_secret} -sha512 -binary | \ + base64 ${base64_args}) + key_id=hmac.key + ;; +*) + usage + ;; +esac + +signature=$(echo -ne "(request-target): ${method,,} ${request}\n(created): ${created}\nhost: ${host}\ndigest: ${digest}" | \ + openssl dgst -sha512 -sign ${private_key} | \ + base64 ${base64_args}) + +case ${method,,} in +post) + curl -v \ + -d ${body} \ + -H "Digest: $digest" \ + -H "Authorization: Signature keyId=${key_id},signature=\"${override_signature:-$signature}\",headers=\"${headers}\",created=${created}" \ + "http://${host}${request}" + ;; +get) + curl -v \ + -H "Digest: $digest" \ + -H "Authorization: Signature keyId=${key_id},signature=\"${override_signature:-$signature}\",headers=\"${headers}\",created=${created}" \ + "http://${host}${request}" + ;; +esac diff --git a/src/plug_signature_http_date.erl b/src/plug_signature_http_date.erl new file mode 100644 index 0000000..c5da5ec --- /dev/null +++ b/src/plug_signature_http_date.erl @@ -0,0 +1,441 @@ +%% Vendored from cowlib 2.7.3, due to build issues with cowlib 1.x. +%% +%% Changes: +%% - changed module name from cow_date, to avoid name clash +%% +%% Original copyright follows: + +%% Copyright (c) 2013-2018, Loïc Hoguin +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(plug_signature_http_date). + +-export([parse_date/1]). +-export([rfc1123/1]). +-export([rfc2109/1]). +-export([rfc7231/1]). + +-ifdef(TEST). +-include_lib("proper/include/proper.hrl"). +-endif. + +%% @doc Parse the HTTP date (IMF-fixdate, rfc850, asctime). + +-define(DIGITS(A, B), ((A - $0) * 10 + (B - $0))). +-define(DIGITS(A, B, C, D), ((A - $0) * 1000 + (B - $0) * 100 + (C - $0) * 10 + (D - $0))). + +-spec parse_date(binary()) -> calendar:datetime(). +parse_date(DateBin) -> + Date = {{_, _, D}, {H, M, S}} = http_date(DateBin), + true = D >= 0 andalso D =< 31, + true = H >= 0 andalso H =< 23, + true = M >= 0 andalso M =< 59, + true = S >= 0 andalso S =< 60, %% Leap second. + Date. + +http_date(<<"Mon, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2)); +http_date(<<"Tue, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2)); +http_date(<<"Wed, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2)); +http_date(<<"Thu, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2)); +http_date(<<"Fri, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2)); +http_date(<<"Sat, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2)); +http_date(<<"Sun, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2)); +http_date(<<"Monday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2)); +http_date(<<"Tuesday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2)); +http_date(<<"Wednesday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2)); +http_date(<<"Thursday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2)); +http_date(<<"Friday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2)); +http_date(<<"Saturday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2)); +http_date(<<"Sunday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2)); +http_date(<<"Mon ", R/bits >>) -> asctime_date(R); +http_date(<<"Tue ", R/bits >>) -> asctime_date(R); +http_date(<<"Wed ", R/bits >>) -> asctime_date(R); +http_date(<<"Thu ", R/bits >>) -> asctime_date(R); +http_date(<<"Fri ", R/bits >>) -> asctime_date(R); +http_date(<<"Sat ", R/bits >>) -> asctime_date(R); +http_date(<<"Sun ", R/bits >>) -> asctime_date(R). + +fixdate(<<"Jan ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 1, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Feb ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 2, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Mar ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 3, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Apr ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 4, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"May ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 5, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Jun ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 6, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Jul ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 7, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Aug ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 8, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Sep ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 9, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Oct ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 10, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Nov ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 11, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +fixdate(<<"Dec ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 12, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}. + +rfc850_date(<<"Jan-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 1, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Feb-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 2, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Mar-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 3, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Apr-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 4, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"May-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 5, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Jun-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 6, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Jul-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 7, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Aug-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 8, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Sep-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 9, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Oct-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 10, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Nov-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 11, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +rfc850_date(<<"Dec-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) -> + {{rfc850_year(?DIGITS(Y1, Y2)), 12, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}. + +rfc850_year(Y) when Y > 50 -> Y + 1900; +rfc850_year(Y) -> Y + 2000. + +asctime_date(<<"Jan ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 1, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Feb ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 2, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Mar ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 3, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Apr ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 4, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"May ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 5, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Jun ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 6, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Jul ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 7, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Aug ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 8, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Sep ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 9, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Oct ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 10, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Nov ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 11, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}; +asctime_date(<<"Dec ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) -> + {{?DIGITS(Y1, Y2, Y3, Y4), 12, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}. + +asctime_day($\s, D2) -> (D2 - $0); +asctime_day(D1, D2) -> (D1 - $0) * 10 + (D2 - $0). + +-ifdef(TEST). +day_name() -> oneof(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]). +day_name_l() -> oneof(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]). +year() -> integer(1951, 2050). +month() -> integer(1, 12). +day() -> integer(1, 31). +hour() -> integer(0, 23). +minute() -> integer(0, 59). +second() -> integer(0, 60). + +fixdate_gen() -> + ?LET({DayName, Y, Mo, D, H, Mi, S}, + {day_name(), year(), month(), day(), hour(), minute(), second()}, + {{{Y, Mo, D}, {H, Mi, S}}, + list_to_binary([DayName, ", ", pad_int(D), " ", month(Mo), " ", integer_to_binary(Y), + " ", pad_int(H), ":", pad_int(Mi), ":", pad_int(S), " GMT"])}). + +rfc850_gen() -> + ?LET({DayName, Y, Mo, D, H, Mi, S}, + {day_name_l(), year(), month(), day(), hour(), minute(), second()}, + {{{Y, Mo, D}, {H, Mi, S}}, + list_to_binary([DayName, ", ", pad_int(D), "-", month(Mo), "-", pad_int(Y rem 100), + " ", pad_int(H), ":", pad_int(Mi), ":", pad_int(S), " GMT"])}). + +asctime_gen() -> + ?LET({DayName, Y, Mo, D, H, Mi, S}, + {day_name(), year(), month(), day(), hour(), minute(), second()}, + {{{Y, Mo, D}, {H, Mi, S}}, + list_to_binary([DayName, " ", month(Mo), " ", + if D < 10 -> << $\s, (D + $0) >>; true -> integer_to_binary(D) end, + " ", pad_int(H), ":", pad_int(Mi), ":", pad_int(S), " ", integer_to_binary(Y)])}). + +prop_http_date() -> + ?FORALL({Date, DateBin}, + oneof([fixdate_gen(), rfc850_gen(), asctime_gen()]), + Date =:= parse_date(DateBin)). + +http_date_test_() -> + Tests = [ + {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}, + {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}, + {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}} + ], + [{V, fun() -> R = http_date(V) end} || {V, R} <- Tests]. + +horse_http_date_fixdate() -> + horse:repeat(200000, + http_date(<<"Sun, 06 Nov 1994 08:49:37 GMT">>) + ). + +horse_http_date_rfc850() -> + horse:repeat(200000, + http_date(<<"Sunday, 06-Nov-94 08:49:37 GMT">>) + ). + +horse_http_date_asctime() -> + horse:repeat(200000, + http_date(<<"Sun Nov 6 08:49:37 1994">>) + ). +-endif. + +%% @doc Return the date formatted according to RFC1123. + +-spec rfc1123(calendar:datetime()) -> binary(). +rfc1123(DateTime) -> + rfc7231(DateTime). + +%% @doc Return the date formatted according to RFC2109. + +-spec rfc2109(calendar:datetime()) -> binary(). +rfc2109({Date = {Y, Mo, D}, {H, Mi, S}}) -> + Wday = calendar:day_of_the_week(Date), + << (weekday(Wday))/binary, ", ", + (pad_int(D))/binary, "-", + (month(Mo))/binary, "-", + (year(Y))/binary, " ", + (pad_int(H))/binary, ":", + (pad_int(Mi))/binary, ":", + (pad_int(S))/binary, " GMT" >>. + +-ifdef(TEST). +rfc2109_test_() -> + Tests = [ + {<<"Sat, 14-May-2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}}, + {<<"Sun, 01-Jan-2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}} + ], + [{R, fun() -> R = rfc2109(D) end} || {R, D} <- Tests]. + +horse_rfc2109_20130101_000000() -> + horse:repeat(100000, + rfc2109({{2013, 1, 1}, {0, 0, 0}}) + ). + +horse_rfc2109_20131231_235959() -> + horse:repeat(100000, + rfc2109({{2013, 12, 31}, {23, 59, 59}}) + ). + +horse_rfc2109_12340506_070809() -> + horse:repeat(100000, + rfc2109({{1234, 5, 6}, {7, 8, 9}}) + ). +-endif. + +%% @doc Return the date formatted according to RFC7231. + +-spec rfc7231(calendar:datetime()) -> binary(). +rfc7231({Date = {Y, Mo, D}, {H, Mi, S}}) -> + Wday = calendar:day_of_the_week(Date), + << (weekday(Wday))/binary, ", ", + (pad_int(D))/binary, " ", + (month(Mo))/binary, " ", + (year(Y))/binary, " ", + (pad_int(H))/binary, ":", + (pad_int(Mi))/binary, ":", + (pad_int(S))/binary, " GMT" >>. + +-ifdef(TEST). +rfc7231_test_() -> + Tests = [ + {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}}, + {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}} + ], + [{R, fun() -> R = rfc7231(D) end} || {R, D} <- Tests]. + +horse_rfc7231_20130101_000000() -> + horse:repeat(100000, + rfc7231({{2013, 1, 1}, {0, 0, 0}}) + ). + +horse_rfc7231_20131231_235959() -> + horse:repeat(100000, + rfc7231({{2013, 12, 31}, {23, 59, 59}}) + ). + +horse_rfc7231_12340506_070809() -> + horse:repeat(100000, + rfc7231({{1234, 5, 6}, {7, 8, 9}}) + ). +-endif. + +%% Internal. + +-spec pad_int(0..59) -> <<_:16>>. +pad_int( 0) -> <<"00">>; +pad_int( 1) -> <<"01">>; +pad_int( 2) -> <<"02">>; +pad_int( 3) -> <<"03">>; +pad_int( 4) -> <<"04">>; +pad_int( 5) -> <<"05">>; +pad_int( 6) -> <<"06">>; +pad_int( 7) -> <<"07">>; +pad_int( 8) -> <<"08">>; +pad_int( 9) -> <<"09">>; +pad_int(10) -> <<"10">>; +pad_int(11) -> <<"11">>; +pad_int(12) -> <<"12">>; +pad_int(13) -> <<"13">>; +pad_int(14) -> <<"14">>; +pad_int(15) -> <<"15">>; +pad_int(16) -> <<"16">>; +pad_int(17) -> <<"17">>; +pad_int(18) -> <<"18">>; +pad_int(19) -> <<"19">>; +pad_int(20) -> <<"20">>; +pad_int(21) -> <<"21">>; +pad_int(22) -> <<"22">>; +pad_int(23) -> <<"23">>; +pad_int(24) -> <<"24">>; +pad_int(25) -> <<"25">>; +pad_int(26) -> <<"26">>; +pad_int(27) -> <<"27">>; +pad_int(28) -> <<"28">>; +pad_int(29) -> <<"29">>; +pad_int(30) -> <<"30">>; +pad_int(31) -> <<"31">>; +pad_int(32) -> <<"32">>; +pad_int(33) -> <<"33">>; +pad_int(34) -> <<"34">>; +pad_int(35) -> <<"35">>; +pad_int(36) -> <<"36">>; +pad_int(37) -> <<"37">>; +pad_int(38) -> <<"38">>; +pad_int(39) -> <<"39">>; +pad_int(40) -> <<"40">>; +pad_int(41) -> <<"41">>; +pad_int(42) -> <<"42">>; +pad_int(43) -> <<"43">>; +pad_int(44) -> <<"44">>; +pad_int(45) -> <<"45">>; +pad_int(46) -> <<"46">>; +pad_int(47) -> <<"47">>; +pad_int(48) -> <<"48">>; +pad_int(49) -> <<"49">>; +pad_int(50) -> <<"50">>; +pad_int(51) -> <<"51">>; +pad_int(52) -> <<"52">>; +pad_int(53) -> <<"53">>; +pad_int(54) -> <<"54">>; +pad_int(55) -> <<"55">>; +pad_int(56) -> <<"56">>; +pad_int(57) -> <<"57">>; +pad_int(58) -> <<"58">>; +pad_int(59) -> <<"59">>; +pad_int(60) -> <<"60">>; +pad_int(Int) -> integer_to_binary(Int). + +-spec weekday(1..7) -> <<_:24>>. +weekday(1) -> <<"Mon">>; +weekday(2) -> <<"Tue">>; +weekday(3) -> <<"Wed">>; +weekday(4) -> <<"Thu">>; +weekday(5) -> <<"Fri">>; +weekday(6) -> <<"Sat">>; +weekday(7) -> <<"Sun">>. + +-spec month(1..12) -> <<_:24>>. +month( 1) -> <<"Jan">>; +month( 2) -> <<"Feb">>; +month( 3) -> <<"Mar">>; +month( 4) -> <<"Apr">>; +month( 5) -> <<"May">>; +month( 6) -> <<"Jun">>; +month( 7) -> <<"Jul">>; +month( 8) -> <<"Aug">>; +month( 9) -> <<"Sep">>; +month(10) -> <<"Oct">>; +month(11) -> <<"Nov">>; +month(12) -> <<"Dec">>. + +-spec year(pos_integer()) -> <<_:32>>. +year(1970) -> <<"1970">>; +year(1971) -> <<"1971">>; +year(1972) -> <<"1972">>; +year(1973) -> <<"1973">>; +year(1974) -> <<"1974">>; +year(1975) -> <<"1975">>; +year(1976) -> <<"1976">>; +year(1977) -> <<"1977">>; +year(1978) -> <<"1978">>; +year(1979) -> <<"1979">>; +year(1980) -> <<"1980">>; +year(1981) -> <<"1981">>; +year(1982) -> <<"1982">>; +year(1983) -> <<"1983">>; +year(1984) -> <<"1984">>; +year(1985) -> <<"1985">>; +year(1986) -> <<"1986">>; +year(1987) -> <<"1987">>; +year(1988) -> <<"1988">>; +year(1989) -> <<"1989">>; +year(1990) -> <<"1990">>; +year(1991) -> <<"1991">>; +year(1992) -> <<"1992">>; +year(1993) -> <<"1993">>; +year(1994) -> <<"1994">>; +year(1995) -> <<"1995">>; +year(1996) -> <<"1996">>; +year(1997) -> <<"1997">>; +year(1998) -> <<"1998">>; +year(1999) -> <<"1999">>; +year(2000) -> <<"2000">>; +year(2001) -> <<"2001">>; +year(2002) -> <<"2002">>; +year(2003) -> <<"2003">>; +year(2004) -> <<"2004">>; +year(2005) -> <<"2005">>; +year(2006) -> <<"2006">>; +year(2007) -> <<"2007">>; +year(2008) -> <<"2008">>; +year(2009) -> <<"2009">>; +year(2010) -> <<"2010">>; +year(2011) -> <<"2011">>; +year(2012) -> <<"2012">>; +year(2013) -> <<"2013">>; +year(2014) -> <<"2014">>; +year(2015) -> <<"2015">>; +year(2016) -> <<"2016">>; +year(2017) -> <<"2017">>; +year(2018) -> <<"2018">>; +year(2019) -> <<"2019">>; +year(2020) -> <<"2020">>; +year(2021) -> <<"2021">>; +year(2022) -> <<"2022">>; +year(2023) -> <<"2023">>; +year(2024) -> <<"2024">>; +year(2025) -> <<"2025">>; +year(2026) -> <<"2026">>; +year(2027) -> <<"2027">>; +year(2028) -> <<"2028">>; +year(2029) -> <<"2029">>; +year(Year) -> integer_to_binary(Year). diff --git a/test/plug_signature/callbacks_test.exs b/test/plug_signature/callbacks_test.exs new file mode 100644 index 0000000..0ad70a5 --- /dev/null +++ b/test/plug_signature/callbacks_test.exs @@ -0,0 +1,214 @@ +defmodule PlugSignature.CallbacksTest do + use ExUnit.Case + doctest PlugSignature.Callback + + import PlugSignature.ConnTest + import ExUnit.CaptureLog + require Logger + + defmodule Callback do + # Sample callback module for use in tests + require Logger + + @behaviour PlugSignature.Callback + + @private_ec X509.PrivateKey.new_ec(:secp256r1) + @public_ec X509.PublicKey.derive(@private_ec) + + @impl true + def client_lookup("no_such_client", _algorithm, _conn) do + {:error, "no client for key_id"} + end + + def client_lookup("key_id", "hs2019", _conn) do + {:ok, "some client", @public_ec} + end + + def client_lookup("key_id", _algorithm, _conn) do + {:error, "no credentials for algorithm"} + end + + def success(conn, client) do + Logger.info("Signature auth successful; client='#{client}'") + Plug.Conn.assign(conn, :client, client) + end + + def failure(conn, "no client for key_id", algorithm, _headers) do + Logger.warn("Invalid key ID (#{algorithm})") + + conn + |> Plug.Conn.send_resp(403, "") + |> Plug.Conn.halt() + end + + def private(), do: @private_ec + end + + setup_all do + [config: [callback_module: Callback]] + end + + describe "PlugSignature.Callback.client_lookup/2" do + test "success", %{config: config} do + priv = config[:callback_module].private() + + conn = + conn() + |> with_signature(priv, "key_id", config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + + test "bad key_id", %{config: config} do + priv = config[:callback_module].private() + + scenario = fn -> + conn = + conn() + |> with_signature(priv, "no_such_client", config) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "no client for key_id" + end + + test "bad algorithm", %{config: config} do + config = Keyword.put(config, :algorithms, ["ecdsa-sha256"]) + priv = config[:callback_module].private() + + scenario = fn -> + conn = + conn() + |> with_signature(priv, "key_id", config) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "no credentials for algorithm" + end + end + + describe "on_success" do + test "{m, f, a}", %{config: config} do + config = Keyword.put(config, :on_success, {Callback, :success, []}) + priv = config[:callback_module].private() + + scenario = fn -> + conn = + conn() + |> with_signature(priv, "key_id", config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + assert conn.assigns[:client] == "some client" + end + + assert capture_log(scenario) =~ "client='some client'" + end + + test "anonymous/2", %{config: config} do + config = + Keyword.put(config, :on_success, fn conn, client -> + Plug.Conn.assign(conn, :client, client) + end) + + priv = config[:callback_module].private() + + conn = + conn() + |> with_signature(priv, "key_id", config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + assert conn.assigns[:client] == "some client" + end + + test "anonymous/1", %{config: config} do + config = + Keyword.put(config, :on_success, fn conn -> + Logger.info("Signature auth successful") + Plug.Conn.assign(conn, :success, true) + end) + + priv = config[:callback_module].private() + + scenario = fn -> + conn = + conn() + |> with_signature(priv, "key_id", config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + assert conn.assigns[:success] == true + end + + assert capture_log(scenario) =~ "Signature auth successful" + end + end + + describe "on_failure" do + test "{m, f, a}", %{config: config} do + config = Keyword.put(config, :on_failure, {Callback, :failure, []}) + priv = config[:callback_module].private() + + scenario = fn -> + conn = + conn() + |> with_signature(priv, "no_such_client", config) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 403 + end + + refute capture_log(scenario) =~ "no client for key_id" + end + + test "anonymous/4", %{config: config} do + config = + Keyword.put(config, :on_failure, fn conn, reason, algorithm, headers -> + Plug.Conn.assign(conn, :auth, {:failed, reason, algorithm, headers}) + end) + + priv = config[:callback_module].private() + + scenario = fn -> + conn = + conn() + |> with_signature(priv, "no_such_client", config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + assert {:failed, "no client for key_id", "hs2019", "(created)"} = conn.assigns[:auth] + end + + refute capture_log(scenario) =~ "no client for key_id" + end + + test "nil", %{config: config} do + config = Keyword.put(config, :on_failure, nil) + priv = config[:callback_module].private() + + scenario = fn -> + conn = + conn() + |> with_signature(priv, "no_such_client", config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + + refute capture_log(scenario) =~ "no client for key_id" + end + end + + defp conn() do + Plug.Test.conn(:get, "/") + end +end diff --git a/test/plug_signature/config_test.exs b/test/plug_signature/config_test.exs new file mode 100644 index 0000000..6d9d670 --- /dev/null +++ b/test/plug_signature/config_test.exs @@ -0,0 +1,289 @@ +defmodule PlugSignature.ConfigTest do + use ExUnit.Case + doctest PlugSignature.Config + + describe "common settings" do + test "requires callback_module" do + assert_raise PlugSignature.ConfigError, "missing mandatory option `:callback_module`", fn -> + PlugSignature.init([]) + end + end + + test "defaults to 'hs2019'" do + opts = PlugSignature.init(callback_module: SomeModule) + assert "hs2019" = Keyword.get(opts, :default_algorithm) + assert ["hs2019"] = opts |> Keyword.get(:algorithms) |> Map.keys() + end + + test "fails on empty algorithms" do + assert_raise PlugSignature.ConfigError, "algorithm list is empty", fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: [] + ) + end + end + + test "supports all algorithms" do + all = ["rsa-sha256", "rsa-sha1", "ecdsa-sha256", "hmac-sha256", "hs2019"] + + opts = + PlugSignature.init( + callback_module: SomeModule, + algorithms: all + ) + + assert "rsa-sha256" = Keyword.get(opts, :default_algorithm) + + Enum.each(all, fn algorithm -> + assert Map.has_key?(Keyword.get(opts, :algorithms), algorithm) + end) + end + + test "fails on unknown algorithm" do + assert_raise PlugSignature.ConfigError, "unknown algorithm: 'ecdsa-sha1'", fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["ecdsa-sha1"] + ) + end + end + + test "checks on_success value" do + assert_raise PlugSignature.ConfigError, "invalid value for `:on_success`", fn -> + PlugSignature.init( + callback_module: SomeModule, + on_success: fn -> :ok end + ) + end + end + + test "checks on_failure value" do + assert_raise PlugSignature.ConfigError, "invalid value for `:on_failure`", fn -> + PlugSignature.init( + callback_module: SomeModule, + on_failure: {SomeModule, :failure} + ) + end + end + end + + describe "hs2019" do + test "allows all pseudo-headers" do + opts = + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019"], + headers: "(created) (expires) (request-target)" + ) + + assert %{"hs2019" => alg_opts} = Keyword.get(opts, :algorithms) + assert "(created) (expires) (request-target)" = alg_opts.headers + assert ["(created)", "(expires)", "(request-target)"] = alg_opts.header_list + end + + test "disallows unknown pseudo-headers" do + assert_raise PlugSignature.ConfigError, "invalid header '(request-method)'", fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019"], + headers: "(created) (request-method)" + ) + end + end + + test "allows any legal HTTP headers" do + opts = + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019"], + headers: "X-Some-Header MD5-digest weird!" + ) + + assert %{"hs2019" => alg_opts} = Keyword.get(opts, :algorithms) + assert "X-Some-Header MD5-digest weird!" = alg_opts.headers + assert ["x-some-header", "md5-digest", "weird!"] = alg_opts.header_list + end + + test "disallows illegal HTTP headers" do + assert_raise PlugSignature.ConfigError, "invalid header 'illegal:header'", fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019"], + headers: "(created) illegal:header" + ) + end + end + + test "allows infinite validity, with (expires)" do + opts = + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019"], + headers: "(request-target) (expires)", + validity: :infinity + ) + + assert %{"hs2019" => alg_opts} = Keyword.get(opts, :algorithms) + assert :infinity = alg_opts.validity + end + + test "disallows infinite validity without (expires)" do + assert_raise PlugSignature.ConfigError, + "missing pseudo-header `(expires)` in header list", + fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019"], + headers: "(request-target) (created)", + validity: :infinity + ) + end + end + + test "checks validity type" do + assert_raise PlugSignature.ConfigError, + "validity must be a range, or `:infinity`", + fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019"], + headers: "(request-target) (created)", + validity: 300 + ) + end + end + end + + describe "legacy algorithms" do + test "allows (request-target) pseudo-headers" do + opts = + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["rsa-sha256"], + headers: "(request-target)" + ) + + assert %{"rsa-sha256" => alg_opts} = Keyword.get(opts, :algorithms) + assert "(request-target)" = alg_opts.headers + assert ["(request-target)"] = alg_opts.header_list + end + + test "disallows other pseudo-headers" do + assert_raise PlugSignature.ConfigError, "invalid header '(created)'", fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["rsa-sha256"], + headers: "(created)" + ) + end + end + + test "allows any legal HTTP headers" do + opts = + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["rsa-sha256"], + headers: "X-Some-Header MD5-digest weird!" + ) + + assert %{"rsa-sha256" => alg_opts} = Keyword.get(opts, :algorithms) + assert "X-Some-Header MD5-digest weird!" = alg_opts.headers + assert ["x-some-header", "md5-digest", "weird!"] = alg_opts.header_list + end + + test "disallows illegal HTTP headers" do + assert_raise PlugSignature.ConfigError, "invalid header 'illegal:header'", fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["rsa-sha256"], + headers: "date illegal:header" + ) + end + end + + test "disallows infinite validity " do + assert_raise PlugSignature.ConfigError, + "cannot use infinite validity with legacy algorithms", + fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["rsa-sha256"], + headers: "(request-target) date", + validity: :infinity + ) + end + end + + test "checks validity type" do + assert_raise PlugSignature.ConfigError, + "validity must be a range", + fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["rsa-sha256"], + headers: "(request-target) date", + validity: 300 + ) + end + end + end + + describe "hs2019 mixed with legacy algorithms" do + test "with default headers and validity" do + opts = + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019", "rsa-sha256"] + ) + + assert %{"hs2019" => hs2019_opts, "rsa-sha256" => legacy_opts} = + Keyword.get(opts, :algorithms) + + assert "(created)" = hs2019_opts.headers + assert -300..30 == hs2019_opts.validity + assert false == hs2019_opts.check_date_header + + assert "date" = legacy_opts.headers + assert -300..30 == legacy_opts.validity + assert true == legacy_opts.check_date_header + end + + test "fails with incompatible headers" do + assert_raise PlugSignature.ConfigError, + "invalid header '(created)'", + fn -> + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019", "rsa-sha256"], + headers: "(request-target) (created) host" + ) + end + end + + test "with custom legacy options" do + opts = + PlugSignature.init( + callback_module: SomeModule, + algorithms: ["hs2019", "rsa-sha256"], + headers: "(request-target) (expires) host", + validity: :infinity, + legacy: [ + headers: "(request-target) date host", + validity: -300..30 + ] + ) + + assert %{"hs2019" => hs2019_opts, "rsa-sha256" => legacy_opts} = + Keyword.get(opts, :algorithms) + + assert "(request-target) (expires) host" = hs2019_opts.headers + assert :infinity = hs2019_opts.validity + assert false == hs2019_opts.check_date_header + + assert "(request-target) date host" = legacy_opts.headers + assert -300..30 == legacy_opts.validity + assert true == legacy_opts.check_date_header + end + end +end diff --git a/test/plug_signature/parser_test.exs b/test/plug_signature/parser_test.exs new file mode 100644 index 0000000..23c924f --- /dev/null +++ b/test/plug_signature/parser_test.exs @@ -0,0 +1,53 @@ +defmodule PlugSignature.ParserTest do + use ExUnit.Case + doctest PlugSignature.Parser + + import PlugSignature.Parser + + test "valid" do + assert {:ok, params} = + authorization( + ~s(Signature keyId=123,signature="0123456789abcdef",created=1562570728) + ) + + assert params[:key_id] == "123" + assert params[:signature] == "0123456789abcdef" + assert params[:created] == "1562570728" + end + + test "extra whitespace" do + assert {:ok, params} = + authorization( + ~s(Signature keyId=123, signature= "0123456789abcdef", created = 1562570728) + ) + + assert params[:key_id] == "123" + assert params[:signature] == "0123456789abcdef" + assert params[:created] == "1562570728" + end + + test "duplicate params" do + assert {:error, "malformed authorization header"} = + authorization( + ~s(Signature keyId=123,signature="0123456789abcdef",created=1562570728,keyId=234) + ) + end + + test "unknown and case mismatch params" do + assert {:ok, params} = + authorization( + ~s(Signature keyColor=red,Signature="0123456789abcdef",created=1562570728) + ) + + refute :keyColor in Keyword.keys(params) + refute :Signature in Keyword.keys(params) + refute :signature in Keyword.keys(params) + assert params[:created] == "1562570728" + end + + test "weird values with quoted characters" do + assert {:ok, params} = authorization(~S(Signature keyId="&^%$#@!,{}=\\\"")) + + assert params[:key_id] == ~S(&^%$#@!,{}=\") + end +end diff --git a/test/plug_signature_test.exs b/test/plug_signature_test.exs new file mode 100644 index 0000000..59f7425 --- /dev/null +++ b/test/plug_signature_test.exs @@ -0,0 +1,467 @@ +defmodule PlugSignatureTest do + use ExUnit.Case + doctest PlugSignature + + import Plug.Conn + import PlugSignature.ConnTest + import ExUnit.CaptureLog + + defmodule Sample do + # Sample callback module for use in tests + @behaviour PlugSignature.Callback + + @private_ec X509.PrivateKey.new_ec(:secp256r1) + @public_ec X509.PublicKey.derive(@private_ec) + @private_rsa X509.PrivateKey.new_rsa(2048) + @public_rsa X509.PublicKey.derive(@private_rsa) + @secret "supersecret" + + @impl true + def client_lookup("ecdsa", "hs2019", _conn), do: {:ok, nil, @public_ec} + def client_lookup("rsa", "hs2019", _conn), do: {:ok, nil, @public_rsa} + def client_lookup("hmac", "hs2019", _conn), do: {:ok, nil, @secret} + def client_lookup(_, "ecdsa-" <> _, _conn), do: {:ok, nil, @public_ec} + def client_lookup(_, "rsa-" <> _, _conn), do: {:ok, nil, @public_rsa} + def client_lookup(_, "hmac" <> _, _conn), do: {:ok, nil, @secret} + + def private("ecdsa"), do: @private_ec + def private("rsa"), do: @private_rsa + def private("hmac"), do: @secret + end + + defp setup_hs2019_ecdsa(_context), do: do_setup("hs2019", "ecdsa") + defp setup_hs2019_rsa(_context), do: do_setup("hs2019", "rsa") + defp setup_hs2019_hmac(_context), do: do_setup("hs2019", "hmac") + defp setup_rsa_sha256(_context), do: do_setup("rsa-sha256", "rsa") + defp setup_rsa_sha1(_context), do: do_setup("rsa-sha1", "rsa") + defp setup_ecdsa_sha256(_context), do: do_setup("ecdsa-sha256", "ecdsa") + defp setup_hmac_sha256(_context), do: do_setup("hmac-sha256", "hmac") + + defp do_setup(algorithm, key_type) do + timestamp = + case algorithm do + "hs2019" -> "(created)" + _ -> "date" + end + + [ + algorithm: algorithm, + key_id: key_type, + key: Sample.private(key_type), + config: [ + callback_module: Sample, + algorithms: [algorithm], + headers: "(request-target) #{timestamp} host", + validity: -60..15 + ] + ] + end + + describe "hs2019 ECDSA" do + setup [:setup_hs2019_ecdsa] + + test "valid", %{config: config, key: key, key_id: key_id} do + conn = + conn() + |> with_signature(key, key_id, config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + + test "valid, expires in the future", %{config: config, key: key, key_id: key_id} do + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :expires_in, 30)) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + + test "invalid, missing Authorization header", %{config: config} do + scenario = fn -> + conn = + conn() + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "no authorization header" + end + + test "invalid, unexpected Authorization scheme", %{config: config} do + scenario = fn -> + conn = + conn() + |> put_req_header("authorization", "Bearer 123") + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "malformed authorization header" + end + + test "invalid, missing keyId", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :key_id_override, "")) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "key key_id not found" + end + + test "invalid, missing signature", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :signature_override, "")) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "key signature not found" + end + + test "invalid, malformed signature", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :signature, "12345")) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "Base64 decoding failed" + end + + test "invalid, insufficient header coverage", %{config: config, key: key, key_id: key_id} do + headers = "(request-target) (created)" + + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :headers, headers)) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "insufficient signature coverage" + end + + test "invalid, mismatched host header", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, config) + |> put_req_header("host", "example.org") + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "incorrect signature" + end + + test "invalid, mismatched optional header", %{config: config, key: key, key_id: key_id} do + headers = "(request-target) (created) host user-agent" + + scenario = fn -> + conn = + conn() + |> put_req_header("user-agent", "test") + |> with_signature(key, key_id, Keyword.put(config, :headers, headers)) + |> put_req_header("user-agent", "wrong") + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "incorrect signature" + end + + test "invalid, missing 'created' timestamp", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :created_override, "")) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "could not build signature_string" + end + + test "invalid, malformed 'created' timestamp", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :created, "10a19")) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "malformed signature creation timestamp" + end + + test "invalid, old 'created' timestamp", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :age, 120)) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "request expired" + end + + test "invalid, future 'created' timestamp", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :age, -60)) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "request timestamp in the future" + end + + test "invalid, expired", %{config: config, key: key, key_id: key_id} do + headers = "(request-target) (created) (expires) host digest" + + config = + config + |> Keyword.put(:headers, headers) + |> Keyword.put(:expires_in, -30) + + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, config) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "request expired" + end + + test "invalid, missing header", %{config: config, key: key, key_id: key_id} do + headers = "(request-target) (created) host digest nosuchheader" + + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :headers, headers)) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "could not build signature_string" + end + + test "invalid, missing expires param", %{config: config, key: key, key_id: key_id} do + headers = "(request-target) (created) (expires) host digest" + + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :headers, headers)) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "could not build signature_string" + end + end + + describe "hs2019 RSA" do + setup [:setup_hs2019_rsa] + + test "valid", %{config: config, key: key, key_id: key_id} do + conn = + conn() + |> with_signature(key, key_id, config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + end + + describe "hs2019 HMAC" do + setup [:setup_hs2019_hmac] + + test "valid", %{config: config, key: key, key_id: key_id} do + conn = + conn() + |> with_signature(key, key_id, config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + end + + describe "rsa-sha256" do + setup [:setup_rsa_sha256] + + test "valid", %{config: config, key: key, key_id: key_id} do + conn = + conn() + |> with_signature(key, key_id, config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + + test "invalid, wrong algorithm", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :algorithms, ["hs2019"])) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "bad signing algorithm" + end + + test "invalid, old Date header", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :age, 120)) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "request expired" + end + + test "invalid, future Date header", %{config: config, key: key, key_id: key_id} do + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :age, -60)) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "request timestamp in the future" + end + + test "invalid, (created) pseudo header", %{config: config, key: key, key_id: key_id} do + headers = "(request-target) (created) date host digest" + + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :headers, headers)) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "could not build signature_string" + end + + test "invalid, (expires) pseudo header", %{config: config, key: key, key_id: key_id} do + headers = "(request-target) (expires) date host digest" + + scenario = fn -> + conn = + conn() + |> with_signature(key, key_id, Keyword.put(config, :headers, headers)) + |> PlugSignature.call(PlugSignature.init(config)) + + assert conn.halted + assert conn.status == 401 + end + + assert capture_log(scenario) =~ "could not build signature_string" + end + end + + describe "rsa-sha" do + setup [:setup_rsa_sha1] + + test "valid", %{config: config, key: key, key_id: key_id} do + conn = + conn() + |> with_signature(key, key_id, config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + end + + describe "ecdsa-sha256" do + setup [:setup_ecdsa_sha256] + + test "valid", %{config: config, key: key, key_id: key_id} do + conn = + conn() + |> with_signature(key, key_id, config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + end + + describe "hmac-sha256" do + setup [:setup_hmac_sha256] + + test "valid", %{config: config, key: key, key_id: key_id} do + conn = + conn() + |> with_signature(key, key_id, config) + |> PlugSignature.call(PlugSignature.init(config)) + + refute conn.halted + end + end + + defp conn() do + host = "localhost:4000" + + Plug.Test.conn(:get, "http://#{host}/") + |> put_req_header("host", host) + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()