diff --git a/modules/activitypub/client.go b/modules/activitypub/client.go index 8a8dd02196..2d6b723c3e 100644 --- a/modules/activitypub/client.go +++ b/modules/activitypub/client.go @@ -619,7 +619,7 @@ func (c *Client) KeyID() string { // Create an http POST request with forgejo/gitea specific headers // -//nolint:dupl + func (c *Client) PostRequest(b []byte, to string) (req *http.Request, err error) { if req, err = c.newRequest(http.MethodPost, b, to); err != nil { return nil, err @@ -628,7 +628,13 @@ func (c *Client) PostRequest(b []byte, to string) (req *http.Request, err error) if c.pubID != "" { if c.useRFC9421 { config := httpsign9421.NewSignConfig().SignCreated(true) - fields := httpsign9421.Headers(setting.Federation.PostHeadersRFC9421...) + + hasBody := len(b) > 0 + sigHeaders := setting.Federation.PostHeadersRFC9421 + if hasBody { + sigHeaders = append(sigHeaders, "Content-Digest") + } + fields := httpsign9421.Headers(sigHeaders...) signers, err := c.SignersRFC9421(config, fields) if err != nil { @@ -636,11 +642,13 @@ func (c *Client) PostRequest(b []byte, to string) (req *http.Request, err error) } req.Header.Set("Created", fmt.Sprintf("%d", time.Now().Unix())) - digest, err := ContentDigest(&req.Body, setting.Federation.DigestAlgorithms) - if err != nil { - return nil, err + if hasBody { + digest, err := ContentDigest(&req.Body, setting.Federation.DigestAlgorithms) + if err != nil { + return nil, err + } + req.Header.Set("Content-Digest", digest) } - req.Header.Set("Content-Digest", digest) for i, signer := range signers { sigName := fmt.Sprintf("sig%d", i+1) @@ -677,7 +685,7 @@ func (c *Client) Get(to string) (resp *http.Response, err error) { // Create an http GET request with forgejo/gitea specific headers // -//nolint:dupl + func (c *Client) GetRequest(to string) (req *http.Request, err error) { if req, err = c.newRequest(http.MethodGet, nil, to); err != nil { return nil, err @@ -694,11 +702,6 @@ func (c *Client) GetRequest(to string) (req *http.Request, err error) { } req.Header.Set("Created", fmt.Sprintf("%d", time.Now().Unix())) - digest, err := ContentDigest(&req.Body, setting.Federation.DigestAlgorithms) - if err != nil { - return nil, err - } - req.Header.Set("Content-Digest", digest) for i, signer := range signers { sigName := fmt.Sprintf("sig%d", i+1) @@ -760,19 +763,27 @@ func (c *Client) GetRFC9421() bool { return c.useRFC9421 } -func (c *Client) SignedHeaders(method string) string { +func (c *Client) SignedHeaders(method string, hasBody bool) string { var ret string switch method { case http.MethodGet: if c.GetRFC9421() { - ret = fmt.Sprintf(`"%v"`, strings.Join(setting.Federation.GetHeadersRFC9421, `" "`)) + headers := setting.Federation.GetHeadersRFC9421 + if hasBody { + headers = append(headers, "Content-Digest") + } + ret = fmt.Sprintf(`"%v"`, strings.Join(headers, `" "`)) } else { ret = strings.Join(setting.Federation.GetHeaders, " ") } case http.MethodPost: if c.GetRFC9421() { - ret = fmt.Sprintf(`"%v"`, strings.Join(setting.Federation.PostHeadersRFC9421, `" "`)) + headers := setting.Federation.PostHeadersRFC9421 + if hasBody { + headers = append(headers, "Content-Digest") + } + ret = fmt.Sprintf(`"%v"`, strings.Join(headers, `" "`)) } else { ret = strings.Join(setting.Federation.PostHeaders, " ") } @@ -806,7 +817,7 @@ type APClient interface { GetRFC9421() bool SetRFC9421(use bool) KeyID() string - SignedHeaders(method string) string + SignedHeaders(method string, hasBody bool) string } // contextKey is a value for use with context.WithValue. diff --git a/modules/activitypub/client_test.go b/modules/activitypub/client_test.go index 205abcad65..1b71354abd 100644 --- a/modules/activitypub/client_test.go +++ b/modules/activitypub/client_test.go @@ -329,16 +329,21 @@ func TestActivityPubSignedPostRFC9421(t *testing.T) { expected := "BODY" srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Regexp(t, "^"+strings.ToLower(setting.Federation.DigestAlgorithm), r.Header.Get("Content-Digest")) + hasBody := r.Header.Get("Content-Digest") != "" + if hasBody { + assert.Regexp(t, "^"+strings.ToLower(setting.Federation.DigestAlgorithm), r.Header.Get("Content-Digest")) + } for _, input := range setting.Federation.PostHeadersRFC9421 { assert.Contains(t, r.Header.Get("Signature-Input"), strings.ToLower(input)) } - assert.Equal(t, activitypub.ActivityStreamsContentType, r.Header.Get("Content-Type")) - require.NoError(t, activitypub.ValidateContentDigest(r.Header.Get("Content-Digest"), &r.Body, digestAlgs)) - body, err := io.ReadAll(r.Body) - require.NoError(t, err) - assert.Equal(t, expected, string(body)) - fmt.Fprint(w, expected) + if hasBody { + assert.Equal(t, activitypub.ActivityStreamsContentType, r.Header.Get("Content-Type")) + require.NoError(t, activitypub.ValidateContentDigest(r.Header.Get("Content-Digest"), &r.Body, digestAlgs)) + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.Equal(t, expected, string(body)) + fmt.Fprint(w, expected) + } })) defer srv.Close() @@ -363,16 +368,21 @@ func TestActivityPubSignedGetRFC9421(t *testing.T) { expected := "" srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Regexp(t, "^"+strings.ToLower(setting.Federation.DigestAlgorithm), r.Header.Get("Content-Digest")) + hasBody := r.Header.Get("Content-Digest") != "" + if hasBody { + assert.Regexp(t, "^"+strings.ToLower(setting.Federation.DigestAlgorithm), r.Header.Get("Content-Digest")) + } for _, input := range setting.Federation.PostHeadersRFC9421 { assert.Contains(t, r.Header.Get("Signature-Input"), strings.ToLower(input)) } - assert.Equal(t, activitypub.ActivityStreamsContentType, r.Header.Get("Content-Type")) - require.NoError(t, activitypub.ValidateContentDigest(r.Header.Get("Content-Digest"), &r.Body, digestAlgs)) - body, err := io.ReadAll(r.Body) - require.NoError(t, err) - assert.Equal(t, expected, string(body)) - fmt.Fprint(w, expected) + if hasBody { + assert.Equal(t, activitypub.ActivityStreamsContentType, r.Header.Get("Content-Type")) + require.NoError(t, activitypub.ValidateContentDigest(r.Header.Get("Content-Digest"), &r.Body, digestAlgs)) + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + assert.Equal(t, expected, string(body)) + fmt.Fprint(w, expected) + } })) defer srv.Close() diff --git a/modules/setting/federation.go b/modules/setting/federation.go index 56f9c97f92..1fa12de32c 100644 --- a/modules/setting/federation.go +++ b/modules/setting/federation.go @@ -63,9 +63,9 @@ var ( DigestAlgorithm: "SHA-256", DigestAlgorithms: []string{"sha-256", "sha-512"}, GetHeaders: []string{"(request-target)", "Date", "Host"}, - GetHeadersRFC9421: []string{"@method", "@target-uri", "Content-Digest", "Created"}, + GetHeadersRFC9421: []string{"@method", "@target-uri", "Created"}, PostHeaders: []string{"(request-target)", "Date", "Host", "Digest"}, - PostHeadersRFC9421: []string{"@method", "@target-uri", "Content-Digest", "Created"}, + PostHeadersRFC9421: []string{"@method", "@target-uri", "Created"}, SignatureEnforced: true, UseRFC9421: false, } diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go index b4d5e5e56d..59a497469c 100644 --- a/routers/api/v1/activitypub/reqsignature.go +++ b/routers/api/v1/activitypub/reqsignature.go @@ -57,15 +57,27 @@ func verifyHTTPMessageSignature(ctx app_context.APIContext) (authenticated bool, if len(sigHeaders) == 0 { return false, fmt.Errorf("missing signature header") } + + hasBody := r.Header.Get("Content-Digest") != "" + var fields httpsign9421.Fields + var headers []string switch r.Method { case http.MethodGet: - fields = httpsign9421.Headers(setting.Federation.GetHeadersRFC9421...) + headers = setting.Federation.GetHeadersRFC9421 + if hasBody { + headers = append(headers, "Content-Digest") + } case http.MethodPost: - fields = httpsign9421.Headers(setting.Federation.PostHeadersRFC9421...) + headers = setting.Federation.PostHeadersRFC9421 + if hasBody { + headers = append(headers, "Content-Digest") + } default: return false, fmt.Errorf("unsupported request type: %v", r.Method) } + + fields = httpsign9421.Headers(headers...) sigNames, err := httpsign9421.RequestSignatureNames(r, false) if err != nil { return false, err @@ -78,8 +90,10 @@ func verifyHTTPMessageSignature(ctx app_context.APIContext) (authenticated bool, config := httpsign9421.NewVerifyConfig() config.SetAllowedAlgs(setting.Federation.SignatureAlgorithmsRFC9421) - if err = activitypub.ValidateContentDigest(r.Header.Get("Content-Digest"), &r.Body, setting.Federation.DigestAlgorithms); err != nil { - return false, fmt.Errorf("invalid HTTP Content-Digest header: %v", err) + if hasBody { + if err = activitypub.ValidateContentDigest(r.Header.Get("Content-Digest"), &r.Body, setting.Federation.DigestAlgorithms); err != nil { + return false, fmt.Errorf("invalid HTTP Content-Digest header: %v", err) + } } for _, name := range sigNames { diff --git a/tests/integration/api_federation_httpsig_test.go b/tests/integration/api_federation_httpsig_test.go index 9df064f70b..707cba9087 100644 --- a/tests/integration/api_federation_httpsig_test.go +++ b/tests/integration/api_federation_httpsig_test.go @@ -97,7 +97,7 @@ func TestFederationHttpSigValidation(t *testing.T) { expKeyID := fmt.Sprintf(`keyId="%v"`, apClient.KeyID()) assert.Contains(t, sig, expKeyID) - expHeaders := fmt.Sprintf(`headers="%v"`, apClient.SignedHeaders(http.MethodGet)) + expHeaders := fmt.Sprintf(`headers="%v"`, apClient.SignedHeaders(http.MethodGet, false)) assert.Contains(t, sig, expHeaders) assert.Contains(t, sig, "signature=") @@ -177,7 +177,7 @@ func TestFederationHttpSigValidation(t *testing.T) { expKeyID := fmt.Sprintf(`keyid="%v"`, apClient.KeyID()) assert.Contains(t, sigInput, expKeyID) - expHeaders := fmt.Sprintf(`sig1=(%v)`, apClient.SignedHeaders(http.MethodGet)) + expHeaders := fmt.Sprintf(`sig1=(%v)`, apClient.SignedHeaders(http.MethodGet, false)) assert.Contains(t, sigInput, expHeaders) resp, err := apClient.Do(req) @@ -265,7 +265,7 @@ func TestFederationHttpSigValidation(t *testing.T) { expKeyID := fmt.Sprintf(`keyId="%v"`, followClient.KeyID()) assert.Contains(t, sig, expKeyID) - expHeaders := fmt.Sprintf(`headers="%v"`, followClient.SignedHeaders(http.MethodPost)) + expHeaders := fmt.Sprintf(`headers="%v"`, followClient.SignedHeaders(http.MethodPost, true)) assert.Contains(t, sig, expHeaders) assert.Contains(t, sig, "signature=") @@ -362,7 +362,7 @@ func TestFederationHttpSigValidation(t *testing.T) { expKeyID := fmt.Sprintf(`keyid="%v"`, followClient.KeyID()) assert.Contains(t, sigInput, expKeyID) - expHeaders := fmt.Sprintf(`sig1=(%v)`, followClient.SignedHeaders(http.MethodPost)) + expHeaders := fmt.Sprintf(`sig1=(%v)`, followClient.SignedHeaders(http.MethodPost, true)) assert.Contains(t, sigInput, expHeaders) resp, err := followClient.Do(req)