Skip to content

Commit ebd49e0

Browse files
authored
fix: Recompute Content-Digest header value when signing content-digest component (#116)
1 parent 0ae7976 commit ebd49e0

3 files changed

Lines changed: 143 additions & 5 deletions

File tree

message_digester.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ type contentDigester struct {
2020
}
2121

2222
func (c contentDigester) update(msg *Message) error {
23-
if val := msg.Header.Get(headerContentDigest); len(val) != 0 {
24-
// header already present. skipping
25-
return nil
26-
}
27-
2823
body, err := c.readBody(msg.Body)
2924
if err != nil {
3025
return err

message_digester_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,62 @@ func TestContentDigesterUpdate(t *testing.T) {
258258
})
259259
},
260260
},
261+
{
262+
uc: "replaces existing content-digest values",
263+
alg: Sha256,
264+
msg: func() *Message {
265+
req, err := http.NewRequestWithContext(
266+
context.TODO(),
267+
http.MethodPost,
268+
"http://example.com/foo",
269+
strings.NewReader(`{"hello": "world"}`),
270+
)
271+
require.NoError(t, err)
272+
273+
req.Header.Add("Content-Digest", "sha-256=:stale-sha256:")
274+
req.Header.Add("Content-Digest", "sha-512=:stale-sha512:")
275+
276+
return MessageFromRequest(req)
277+
}(),
278+
assert: func(t *testing.T, err error, msg *Message) {
279+
t.Helper()
280+
281+
require.NoError(t, err)
282+
283+
require.Equal(t, []string{
284+
"sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:",
285+
}, msg.Header.Values("Content-Digest"))
286+
},
287+
},
288+
{
289+
uc: "replaces existing content-digest values without specified algorithm",
290+
msg: func() *Message {
291+
req, err := http.NewRequestWithContext(
292+
context.TODO(),
293+
http.MethodPost,
294+
"http://example.com/foo",
295+
strings.NewReader(`{"hello": "world"}`),
296+
)
297+
require.NoError(t, err)
298+
299+
req.Header.Set("Content-Digest", "sha-256=:stale:")
300+
301+
return MessageFromRequest(req)
302+
}(),
303+
assert: func(t *testing.T, err error, msg *Message) {
304+
t.Helper()
305+
306+
require.NoError(t, err)
307+
308+
values := strings.Split(msg.Header.Get("Content-Digest"), ", ")
309+
assert.ElementsMatch(t, values, []string{
310+
"sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:",
311+
"sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:",
312+
})
313+
314+
assert.NotContains(t, msg.Header.Get("Content-Digest"), "stale")
315+
},
316+
},
261317
} {
262318
t.Run(tc.uc, func(t *testing.T) {
263319
cd := &contentDigester{alg: supportedAlgs[tc.alg], algName: tc.alg}

signer_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ func TestSignerSign(t *testing.T) {
457457
WithTTL(0),
458458
WithComponents("@authority", "content-digest", "@query-param;name=\"Pet\""),
459459
WithTag("header-example"),
460+
WithContentDigestAlgorithm(Sha512),
460461
},
461462
msg: &Message{
462463
Method: http.MethodPost,
@@ -498,6 +499,7 @@ func TestSignerSign(t *testing.T) {
498499
WithComponents(
499500
"date", "@method", "@path", "@query", "@authority", "content-type", "content-digest", "content-length",
500501
),
502+
WithContentDigestAlgorithm(Sha512),
501503
},
502504
msg: &Message{
503505
Method: http.MethodPost,
@@ -537,6 +539,7 @@ func TestSignerSign(t *testing.T) {
537539
WithLabel("sig-b24"),
538540
WithTTL(0),
539541
WithComponents("@status", "content-type", "content-digest", "content-length"),
542+
WithContentDigestAlgorithm(Sha512),
540543
},
541544
msg: &Message{
542545
Method: http.MethodPost,
@@ -635,6 +638,90 @@ func TestSignerSign(t *testing.T) {
635638
assert.Equal(t, "sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==:", sig)
636639
},
637640
},
641+
{
642+
uc: "overwrites stale content-digest when signing content-digest component",
643+
key: Key{KeyID: "test-key-rsa-pss", Algorithm: RsaPssSha512, Key: tkRSAPSS},
644+
opts: []SignerOption{
645+
WithNonce(NonceGetterFunc(func(_ context.Context) (string, error) { return "", nil })),
646+
WithLabel("sig-content-digest"),
647+
WithTTL(0),
648+
WithComponents("content-digest"),
649+
WithContentDigestAlgorithm(Sha256),
650+
},
651+
msg: &Message{
652+
Method: http.MethodPost,
653+
Authority: "example.com",
654+
URL: testURL,
655+
Header: http.Header{
656+
"Host": []string{"example.com"},
657+
"Date": []string{"Tue, 20 Apr 2021 02:07:55 GMT"},
658+
"Content-Type": []string{"application/json"},
659+
"Content-Digest": []string{"sha-256=:stale:"},
660+
"Content-Length": []string{"18"},
661+
},
662+
IsRequest: true,
663+
Body: func() (io.ReadCloser, error) {
664+
return io.NopCloser(strings.NewReader(`{"hello": "world"}`)), nil
665+
},
666+
},
667+
assert: func(t *testing.T, err error, header http.Header) {
668+
t.Helper()
669+
670+
require.NoError(t, err)
671+
672+
assert.Equal(
673+
t,
674+
"sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:",
675+
header.Get("Content-Digest"),
676+
)
677+
678+
sigInput := header.Get("Signature-Input")
679+
assert.Equal(t, `sig-content-digest=("content-digest");created=1618884473;keyid="test-key-rsa-pss"`, sigInput)
680+
681+
sig := header.Get("Signature")
682+
assert.True(t, strings.HasPrefix(sig, "sig-content-digest=:"))
683+
},
684+
},
685+
{
686+
uc: "preserves existing content-digest when content-digest component is not signed",
687+
key: Key{KeyID: "test-key-rsa-pss", Algorithm: RsaPssSha512, Key: tkRSAPSS},
688+
opts: []SignerOption{
689+
WithNonce(NonceGetterFunc(func(_ context.Context) (string, error) { return "", nil })),
690+
WithLabel("sig-no-content-digest"),
691+
WithTTL(0),
692+
WithComponents("date", "@method"),
693+
WithContentDigestAlgorithm(Sha256),
694+
},
695+
msg: &Message{
696+
Method: http.MethodPost,
697+
Authority: "example.com",
698+
URL: testURL,
699+
Header: http.Header{
700+
"Host": []string{"example.com"},
701+
"Date": []string{"Tue, 20 Apr 2021 02:07:55 GMT"},
702+
"Content-Type": []string{"application/json"},
703+
"Content-Digest": []string{"sha-256=:stale:"},
704+
"Content-Length": []string{"18"},
705+
},
706+
IsRequest: true,
707+
Body: func() (io.ReadCloser, error) {
708+
return io.NopCloser(strings.NewReader(`{"hello": "world"}`)), nil
709+
},
710+
},
711+
assert: func(t *testing.T, err error, header http.Header) {
712+
t.Helper()
713+
714+
require.NoError(t, err)
715+
716+
assert.Equal(t, "sha-256=:stale:", header.Get("Content-Digest"))
717+
718+
sigInput := header.Get("Signature-Input")
719+
assert.Equal(t, `sig-no-content-digest=("date" "@method");created=1618884473;keyid="test-key-rsa-pss"`, sigInput)
720+
721+
sig := header.Get("Signature")
722+
assert.True(t, strings.HasPrefix(sig, "sig-no-content-digest=:"))
723+
},
724+
},
638725
} {
639726
t.Run(tc.uc, func(t *testing.T) {
640727
s, err := NewSigner(tc.key, tc.opts...)

0 commit comments

Comments
 (0)