From d7424142def63123ddab3bfdf8898e38140e5333 Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Fri, 15 May 2026 13:02:23 +0200 Subject: [PATCH 1/2] fix: Rejecting duplicate component identifier --- signature_parameters.go | 12 +++++ signature_parameters_test.go | 92 +++++++++++++++++++++++++++++++----- 2 files changed, 91 insertions(+), 13 deletions(-) diff --git a/signature_parameters.go b/signature_parameters.go index 08d7c3a..56084b6 100644 --- a/signature_parameters.go +++ b/signature_parameters.go @@ -145,8 +145,20 @@ func (p *signatureParameters) toSignatureBase(msg *Message) ([]byte, error) { } sigBase := make(componentList, 0, len(p.identifiers)) + seen := make(map[string]struct{}, len(p.identifiers)) for _, ci := range p.identifiers { + identifier, err := httpsfv.Marshal(ci.Item) + if err != nil { + return nil, err + } + + if _, ok := seen[identifier]; ok { + return nil, fmt.Errorf("%w: duplicate component identifier %s", ErrMalformedData, identifier) + } + + seen[identifier] = struct{}{} + comp, err := ci.createComponent(msg) if err != nil { return nil, err diff --git a/signature_parameters_test.go b/signature_parameters_test.go index a14dfaa..e55db7e 100644 --- a/signature_parameters_test.go +++ b/signature_parameters_test.go @@ -488,7 +488,7 @@ func TestSignatureParametersToSignatureBase(t *testing.T) { // test is based on the non-normative example from // https://www.rfc-editor.org/rfc/rfc9421.html#name-creating-the-signature-base - reqURL, err := url.Parse("http://example.com/foo?param=Value&Pet=dog") + reqURL, err := url.Parse("http://example.com/foo?param=Value&Pet=dog&Cat=meow") require.NoError(t, err) msg := &Message{ @@ -506,7 +506,26 @@ func TestSignatureParametersToSignatureBase(t *testing.T) { IsRequest: true, } - expectedSigBase := `"@method": POST + keyID := "test-key-rsa-pss" + created, err := time.Parse(time.RFC1123, "Tue, 20 Apr 2021 02:07:53 GMT") + require.NoError(t, err) + + for _, tc := range []struct { + uc string + components []string + assert func(t *testing.T, err error, sigBase []byte) + }{ + { + uc: "successful signature base creation", + components: []string{ + "@method", "@authority", "@path", "content-digest", "content-length", "content-type", + }, + assert: func(t *testing.T, err error, sigBase []byte) { + t.Helper() + + require.NoError(t, err) + + expected := `"@method": POST "@authority": example.com "@path": /foo "content-digest": sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==: @@ -514,19 +533,66 @@ func TestSignatureParametersToSignatureBase(t *testing.T) { "content-type": application/json "@signature-params": ("@method" "@authority" "@path" "content-digest" "content-length" "content-type");created=1618884473;keyid="test-key-rsa-pss"` - identifiers, err := toComponentIdentifiers([]string{ - "@method", "@authority", "@path", "content-digest", "content-length", "content-type", - }) - require.NoError(t, err) + assert.Equal(t, expected, string(sigBase)) + }, + }, + { + uc: "failing for duplicate component identifier", + components: []string{ + "@method", "@method", + }, + assert: func(t *testing.T, err error, sigBase []byte) { + t.Helper() - keyID := "test-key-rsa-pss" - created, err := time.Parse(time.RFC1123, "Tue, 20 Apr 2021 02:07:53 GMT") - require.NoError(t, err) + require.Error(t, err) + require.ErrorIs(t, err, ErrMalformedData) + require.ErrorContains(t, err, "duplicate component identifier") + assert.Nil(t, sigBase) + }, + }, + { + uc: "failing for duplicate parameterized component identifier", + components: []string{ + `@query-param;name="Pet"`, + `@query-param;name="Pet"`, + }, + assert: func(t *testing.T, err error, sigBase []byte) { + t.Helper() - params := newSignatureParameters(created, time.Time{}, "", keyID, "", "", identifiers) + require.Error(t, err) + require.ErrorIs(t, err, ErrMalformedData) + require.ErrorContains(t, err, "duplicate component identifier") + assert.Nil(t, sigBase) + }, + }, + { + uc: "allowing same component identifier with different parameters", + components: []string{ + `@query-param;name="Pet"`, + `@query-param;name="Cat"`, + }, + assert: func(t *testing.T, err error, sigBase []byte) { + t.Helper() - sigBase, err := params.toSignatureBase(msg) - require.NoError(t, err) + require.NoError(t, err) + + expected := `"@query-param";name="Pet": dog +"@query-param";name="Cat": meow +"@signature-params": ("@query-param";name="Pet" "@query-param";name="Cat");created=1618884473;keyid="test-key-rsa-pss"` - assert.Equal(t, expectedSigBase, string(sigBase)) + assert.Equal(t, expected, string(sigBase)) + }, + }, + } { + t.Run(tc.uc, func(t *testing.T) { + identifiers, err := toComponentIdentifiers(tc.components) + require.NoError(t, err) + + params := newSignatureParameters(created, time.Time{}, "", keyID, "", "", identifiers) + + sigBase, err := params.toSignatureBase(msg) + + tc.assert(t, err, sigBase) + }) + } } From c8161a4b1c60a3b53b03ddf83059a554c362d7fc Mon Sep 17 00:00:00 2001 From: Dimitrij Drus Date: Fri, 15 May 2026 13:10:49 +0200 Subject: [PATCH 2/2] better test coverage --- signature_parameters_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/signature_parameters_test.go b/signature_parameters_test.go index e55db7e..40e6390 100644 --- a/signature_parameters_test.go +++ b/signature_parameters_test.go @@ -565,6 +565,18 @@ func TestSignatureParametersToSignatureBase(t *testing.T) { assert.Nil(t, sigBase) }, }, + { + uc: "failing for unknown component identifier", + components: []string{"@status"}, + assert: func(t *testing.T, err error, sigBase []byte) { + t.Helper() + + require.Error(t, err) + require.ErrorIs(t, err, ErrCanonicalization) + require.ErrorContains(t, err, "@status") + assert.Nil(t, sigBase) + }, + }, { uc: "allowing same component identifier with different parameters", components: []string{