HttpSignaturePlug: accept standard (request-target)

The (request-target) used by Pleroma is non-standard, but many HTTP
signature implementations do it this way due to a misinterpretation of
the draft 06 of HTTP signatures: "path" was interpreted as not having
the query, though later examples show that it must be the absolute path
with the query part of the URL as well.

This behavior is kept to make sure most software (Pleroma itself,
Mastodon, and probably others) do not break, but Pleroma now accepts
signatures for a (request-target) containing the query, as expected by
many HTTP signature libraries, and clarified in the draft 11 of HTTP
signatures.

Additionally, the new draft renamed (request-target) to @request-target.
We now support both for incoming requests' signatures.
This commit is contained in:
Hélène 2022-08-17 03:30:02 +02:00
parent f41d970a59
commit 61254111e5
No known key found for this signature in database
GPG Key ID: A215F2E9F1589D62
1 changed files with 45 additions and 8 deletions

View File

@ -25,21 +25,58 @@ def call(conn, _opts) do
end end
end end
defp maybe_assign_valid_signature(conn) do defp validate_signature(conn, request_target) do
if has_signature_header?(conn) do # Newer drafts for HTTP signatures now use @request-target instead of the
# set (request-target) header to the appropriate value # old (request-target). We'll now support both for incoming signatures.
# we also replace the digest header with the one we computed
request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"
conn = conn =
conn conn
|> put_req_header("(request-target)", request_target) |> put_req_header("(request-target)", request_target)
|> case do |> put_req_header("@request-target", request_target)
HTTPSignatures.validate_conn(conn)
end
defp validate_signature(conn) do
# This (request-target) is non-standard, but many implementations do it
# this way due to a misinterpretation of
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-06
# "path" was interpreted as not having the query, though later examples
# show that it must be the absolute path + query. This behavior is kept to
# make sure most software (Pleroma itself, Mastodon, and probably others)
# do not break.
request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"
# This is the proper way to build the @request-target, as expected by
# many HTTP signature libraries, clarified in the following draft:
# https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-11.html#section-2.2.6
# It is the same as before, but containing the query part as well.
proper_target = request_target <> "?#{conn.query_string}"
cond do
# Normal, non-standard behavior but expected by Pleroma and more.
validate_signature(conn, request_target) ->
true
# Has query string and the previous one failed: let's try the standard.
conn.query_string != "" ->
validate_signature(conn, proper_target)
# If there's no query string and signature fails, it's rotten.
true ->
false
end
end
defp maybe_assign_valid_signature(conn) do
if has_signature_header?(conn) do
# we replace the digest header with the one we computed in DigestPlug
conn =
case conn do
%{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest) %{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
conn -> conn conn -> conn
end end
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn)) assign(conn, :valid_signature, validate_signature(conn))
else else
Logger.debug("No signature header!") Logger.debug("No signature header!")
conn conn