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:
parent
f41d970a59
commit
61254111e5
1 changed files with 45 additions and 8 deletions
|
@ -25,21 +25,58 @@ def call(conn, _opts) do
|
|||
end
|
||||
end
|
||||
|
||||
defp validate_signature(conn, request_target) do
|
||||
# Newer drafts for HTTP signatures now use @request-target instead of the
|
||||
# old (request-target). We'll now support both for incoming signatures.
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("(request-target)", request_target)
|
||||
|> 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
|
||||
# set (request-target) header to the appropriate value
|
||||
# we also replace the digest header with the one we computed
|
||||
request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"
|
||||
|
||||
# we replace the digest header with the one we computed in DigestPlug
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("(request-target)", request_target)
|
||||
|> case do
|
||||
case conn do
|
||||
%{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
|
||||
conn -> conn
|
||||
end
|
||||
|
||||
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
|
||||
assign(conn, :valid_signature, validate_signature(conn))
|
||||
else
|
||||
Logger.debug("No signature header!")
|
||||
conn
|
||||
|
|
Loading…
Reference in a new issue