Skip to content

Commit 5cc16ea

Browse files
committed
Trailer fields (partial), bump SFV
1 parent 640bf0d commit 5cc16ea

File tree

5 files changed

+127
-77
lines changed

5 files changed

+127
-77
lines changed

fields.go

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ func (f field) headerName() (bool, string) {
103103

104104
// AddHeader appends a bare header name, e.g. "cache-control".
105105
func (fs *Fields) AddHeader(hdr string) *Fields {
106-
return fs.AddHeaderExt(hdr, false, false, false)
106+
return fs.AddHeaderExt(hdr, false, false, false, false)
107107
}
108108

109109
// AddHeaderExt appends a bare header name, e.g. "cache-control". See type documentation
110110
// for details on optional parameters. The component can be marked as coming from an associated request.
111-
func (fs *Fields) AddHeaderExt(hdr string, optional, binarySequence, associatedRequest bool) *Fields {
111+
func (fs *Fields) AddHeaderExt(hdr string, optional bool, binarySequence bool, associatedRequest bool, trailer bool) *Fields {
112112
f := fromHeaderName(hdr)
113-
f.markField(optional, associatedRequest)
113+
f.markField(optional, associatedRequest, trailer)
114114
if binarySequence {
115115
f.markBinarySequence()
116116
}
@@ -139,14 +139,14 @@ func (f field) queryParam() (bool, string) {
139139

140140
// AddQueryParam indicates a request for a specific query parameter to be signed.
141141
func (fs *Fields) AddQueryParam(qp string) *Fields {
142-
return fs.AddQueryParamExt(qp, false, false)
142+
return fs.AddQueryParamExt(qp, false, false, false)
143143
}
144144

145145
// AddQueryParamExt indicates a request for a specific query parameter to be signed. See type documentation
146146
// for details on optional parameters. The component can be marked as coming from an associated request.
147-
func (fs *Fields) AddQueryParamExt(qp string, optional, associatedRequest bool) *Fields {
147+
func (fs *Fields) AddQueryParamExt(qp string, optional, associatedRequest, trailer bool) *Fields {
148148
f := fromQueryParam(qp)
149-
f.markField(optional, associatedRequest)
149+
f.markField(optional, associatedRequest, trailer)
150150
fs.f = append(fs.f, *f)
151151
return fs
152152
}
@@ -169,15 +169,15 @@ func (f field) dictHeader() (ok bool, hdr, key string) {
169169

170170
// AddDictHeader indicates that out of a header structured as a dictionary, a specific key value is signed/verified.
171171
func (fs *Fields) AddDictHeader(hdr, key string) *Fields {
172-
return fs.AddDictHeaderExt(hdr, key, false, false)
172+
return fs.AddDictHeaderExt(hdr, key, false, false, false)
173173
}
174174

175175
// AddDictHeaderExt indicates that out of a header structured as a dictionary, a specific key value is signed/verified.
176176
// See type documentation
177177
// for details on optional parameters. The component can be marked as coming from an associated request.
178-
func (fs *Fields) AddDictHeaderExt(hdr, key string, optional, associatedRequest bool) *Fields {
178+
func (fs *Fields) AddDictHeaderExt(hdr, key string, optional, associatedRequest, trailer bool) *Fields {
179179
f := fromDictHeader(hdr, key)
180-
f.markField(optional, associatedRequest)
180+
f.markField(optional, associatedRequest, trailer)
181181
fs.f = append(fs.f, *f)
182182
return fs
183183
}
@@ -200,17 +200,32 @@ func (f field) binarySequence() bool {
200200
return ok && v.(bool)
201201
}
202202

203+
func (f field) trailer() bool {
204+
v, ok := f.Params.Get("tr")
205+
return ok && v.(bool)
206+
}
207+
208+
func (f field) optional() bool {
209+
v, ok := f.Params.Get("optional")
210+
return ok && v.(bool)
211+
}
212+
213+
func (f field) associatedRequest() bool {
214+
v, ok := f.Params.Get("req")
215+
return ok && v.(bool)
216+
}
217+
203218
// AddStructuredField indicates that a header should be interpreted as a structured field, per RFC 8941.
204219
func (fs *Fields) AddStructuredField(hdr string) *Fields {
205-
return fs.AddStructuredFieldExt(hdr, false, false)
220+
return fs.AddStructuredFieldExt(hdr, false, false, false)
206221
}
207222

208223
// AddStructuredFieldExt indicates that a header should be interpreted as a structured field, per RFC 8941.
209224
// See type documentation
210225
// for details on optional parameters. The component can be marked as coming from an associated request.
211-
func (fs *Fields) AddStructuredFieldExt(hdr string, optional, associatedRequest bool) *Fields {
226+
func (fs *Fields) AddStructuredFieldExt(hdr string, optional, associatedRequest, trailer bool) *Fields {
212227
f := fromStructuredField(hdr)
213-
f.markField(optional, associatedRequest)
228+
f.markField(optional, associatedRequest, trailer)
214229
fs.f = append(fs.f, *f)
215230
return fs
216231
}
@@ -224,34 +239,39 @@ func (f field) asSignatureBase() (string, error) {
224239
return s, err
225240
}
226241

227-
func (f field) markField(optional bool, associatedRequest bool) {
242+
func (f field) markField(optional bool, associatedRequest bool, trailer bool) {
228243
if optional {
229244
f.markOptional()
230245
}
231246
if associatedRequest {
232247
f.markAssociatedRequest()
233248
}
249+
if trailer {
250+
f.markTrailer()
251+
}
234252
}
235253

236-
func (f field) markOptional() {
254+
func (f field) markFlag(name string) {
237255
if f.Params == nil {
238256
f.Params = httpsfv.NewParams()
239257
}
240-
f.Params.Add("optional", true)
258+
f.Params.Add(name, true)
259+
}
260+
261+
func (f field) markOptional() {
262+
f.markFlag("optional")
241263
}
242264

243265
func (f field) markBinarySequence() {
244-
if f.Params == nil {
245-
f.Params = httpsfv.NewParams()
246-
}
247-
f.Params.Add("bs", true)
266+
f.markFlag("bs")
248267
}
249268

250269
func (f field) markAssociatedRequest() {
251-
if f.Params == nil {
252-
f.Params = httpsfv.NewParams()
253-
}
254-
f.Params.Add("req", true)
270+
f.markFlag("req")
271+
}
272+
273+
func (f field) markTrailer() {
274+
f.markFlag("tr")
255275
}
256276

257277
func (f field) unmarkOptional() {
@@ -261,45 +281,6 @@ func (f field) unmarkOptional() {
261281
f.Params.Del("optional")
262282
}
263283

264-
func (f field) isOptional() bool {
265-
if f.Params != nil {
266-
v, ok := f.Params.Get("optional")
267-
if ok {
268-
vv, ok2 := v.(bool)
269-
if ok2 {
270-
return vv
271-
}
272-
}
273-
}
274-
return false
275-
}
276-
277-
func (f field) isBinarySequence() bool {
278-
if f.Params != nil {
279-
v, ok := f.Params.Get("bs")
280-
if ok {
281-
vv, ok2 := v.(bool)
282-
if ok2 {
283-
return vv
284-
}
285-
}
286-
}
287-
return false
288-
}
289-
290-
func (f field) forAssociatedRequest() bool {
291-
if f.Params != nil {
292-
v, ok := f.Params.Get("req")
293-
if ok {
294-
vv, ok2 := v.(bool)
295-
if ok2 {
296-
return vv
297-
}
298-
}
299-
}
300-
return false
301-
}
302-
303284
// Not a full deep copy, but good enough for mutating params
304285
func (f field) copy() field {
305286
ff := field{
@@ -330,7 +311,7 @@ func (fs *Fields) asSignatureInput(p *httpsfv.Params) (string, error) {
330311
return s, err
331312
}
332313

333-
// contains verifies that all required fields are in the given list of fields (yes, this is O(n^2)).
314+
// contains verifies that all required fields are in the given list of fields (yes, this is O(n^2)).
334315
func (fs *Fields) contains(requiredFields *Fields) bool {
335316
outer:
336317
for _, f1 := range requiredFields.f {

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.19
44

55
require (
66
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
7-
github.com/dunglas/httpsfv v1.0.0
7+
github.com/dunglas/httpsfv v1.0.1
88
github.com/lestrrat-go/jwx/v2 v2.0.6
99
github.com/stretchr/testify v1.8.0
1010
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
66
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
77
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
88
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
9-
github.com/dunglas/httpsfv v1.0.0 h1:ckJImqjT9j782rxCxRXC/4YyFe2FJtCINs6Qs/xtIdU=
10-
github.com/dunglas/httpsfv v1.0.0/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
9+
github.com/dunglas/httpsfv v1.0.1 h1:OjTvfzHJuwjuoyUPkDL1lJzcUP//AUd7cWvn1Nvo03w=
10+
github.com/dunglas/httpsfv v1.0.1/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
1111
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
1212
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
1313
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=

signatures_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,7 +1474,7 @@ func TestVerifyResponse(t *testing.T) {
14741474

14751475
func TestOptionalSign(t *testing.T) {
14761476
req := readRequest(httpreq2)
1477-
f1 := NewFields().AddHeader("date").AddHeaderExt("x-optional", true, false, false)
1477+
f1 := NewFields().AddHeader("date").AddHeaderExt("x-optional", true, false, false, false)
14781478
key1 := bytes.Repeat([]byte{0x55}, 64)
14791479
signer1, err := NewHMACSHA256Signer("key1", key1, NewSignConfig().setFakeCreated(9999), *f1)
14801480
assert.NoError(t, err, "Could not create signer")
@@ -1489,7 +1489,7 @@ func TestOptionalSign(t *testing.T) {
14891489
assert.Equal(t, "sig1=(\"date\" \"x-optional\");created=9999;alg=\"hmac-sha256\";keyid=\"key1\"", signatureInput)
14901490
assert.Equal(t, "\"date\": Tue, 20 Apr 2021 02:07:55 GMT\n\"x-optional\": value\n\"@signature-params\": (\"date\" \"x-optional\");created=9999;alg=\"hmac-sha256\";keyid=\"key1\"", signatureBase)
14911491

1492-
f2 := f1.AddQueryParamExt("bla", true, false).AddQueryParamExt("bar", true, false)
1492+
f2 := f1.AddQueryParamExt("bla", true, false, false).AddQueryParamExt("bar", true, false, false)
14931493
signer2, err := NewHMACSHA256Signer("key1", key1, NewSignConfig().setFakeCreated(9999), *f2)
14941494
assert.NoError(t, err, "Could not create signer")
14951495
signatureInput, _, signatureBase, err = signRequestDebug("sig1", *signer2, req)
@@ -1499,7 +1499,7 @@ func TestOptionalSign(t *testing.T) {
14991499

15001500
res1 := readResponse(httpres2)
15011501
res1.Header.Set("X-Dictionary", "a=1, b=2;x=1;y=2, c=(a b c)")
1502-
f3 := NewFields().AddDictHeaderExt("x-dictionary", "a", true, false).AddDictHeaderExt("x-dictionary", "zz", true, false)
1502+
f3 := NewFields().AddDictHeaderExt("x-dictionary", "a", true, false, false).AddDictHeaderExt("x-dictionary", "zz", true, false, false)
15031503
signer3, err := NewHMACSHA256Signer("key1", key1, NewSignConfig().setFakeCreated(9999), *f3)
15041504
assert.NoError(t, err, "Could not create signer")
15051505
signatureInput, _, signatureBase, err = signResponseDebug("sig1", *signer3, res1, nil)
@@ -1509,7 +1509,7 @@ func TestOptionalSign(t *testing.T) {
15091509

15101510
res2 := readResponse(httpres2)
15111511
res2.Header.Set("X-Dictionary", "a=1, b=2;x=1;y=2, c=(a b c)")
1512-
f4 := NewFields().AddStructuredFieldExt("x-dictionary", true, false).AddStructuredFieldExt("x-not-a-dictionary", true, false)
1512+
f4 := NewFields().AddStructuredFieldExt("x-dictionary", true, false, false).AddStructuredFieldExt("x-not-a-dictionary", true, false, false)
15131513
signer4, err := NewHMACSHA256Signer("key1", key1, NewSignConfig().setFakeCreated(9999), *f4)
15141514
assert.NoError(t, err, "Could not create signer")
15151515
signatureInput, _, signatureBase, err = signResponseDebug("sig1", *signer4, res2, nil)
@@ -1523,8 +1523,8 @@ func TestAssocMessage(t *testing.T) {
15231523
assocReq := readRequest(httpreq2)
15241524
res1 := readResponse(httpres2)
15251525
res1.Header.Set("X-Dictionary", "a=1, b=2;x=1;y=2, c=(a b c)")
1526-
f3 := NewFields().AddDictHeaderExt("x-dictionary", "a", true, false).AddDictHeaderExt("x-dictionary", "zz", true, false).
1527-
AddQueryParamExt("pet", false, true)
1526+
f3 := NewFields().AddDictHeaderExt("x-dictionary", "a", true, false, false).AddDictHeaderExt("x-dictionary", "zz", true, false, false).
1527+
AddQueryParamExt("pet", false, true, false)
15281528
signer3, err := NewHMACSHA256Signer("key1", key1, NewSignConfig().setFakeCreated(9999), *f3)
15291529
assert.NoError(t, err, "Could not create signer")
15301530
signatureInput, signature, signatureBase, err := signResponseDebug("sig1", *signer3, res1, assocReq)
@@ -1590,7 +1590,7 @@ func TestRequestBinding(t *testing.T) {
15901590
func TestOptionalVerify(t *testing.T) {
15911591
req := readRequest(httpreq2)
15921592
req.Header.Add("X-Opt1", "val1")
1593-
f1 := NewFields().AddHeader("date").AddHeaderExt("x-opt1", true, false, false)
1593+
f1 := NewFields().AddHeader("date").AddHeaderExt("x-opt1", true, false, false, false)
15941594
key1 := bytes.Repeat([]byte{0x66}, 64)
15951595
signer, err := NewHMACSHA256Signer("key1", key1, NewSignConfig().setFakeCreated(8888), *f1)
15961596
assert.NoError(t, err, "Could not create signer")
@@ -1635,13 +1635,13 @@ func TestBinarySequence(t *testing.T) {
16351635

16361636
// First signature try fails
16371637
signer1, err := NewP256Signer("key20", *priv, NewSignConfig(),
1638-
*NewFields().AddHeader("@status").AddHeaderExt("set-cookie", false, false, false))
1638+
*NewFields().AddHeader("@status").AddHeaderExt("set-cookie", false, false, false, false))
16391639
assert.NoError(t, err, "could not create signer")
16401640
_, _, err = SignResponse("sig2", *signer1, res, nil)
16411641
assert.Error(t, err, "signature should have failed")
16421642

16431643
signer2, err := NewP256Signer("key20", *priv, NewSignConfig().setFakeCreated(1659563420),
1644-
*NewFields().AddHeader("@status").AddHeaderExt("set-cookie", false, true, false))
1644+
*NewFields().AddHeader("@status").AddHeaderExt("set-cookie", false, true, false, false))
16451645
assert.NoError(t, err, "could not create signer")
16461646
sigInput, sig, sigBase, err := signResponseDebug("sig2", *signer2, res, nil)
16471647
assert.NoError(t, err, "could not sign response")
@@ -1651,14 +1651,14 @@ func TestBinarySequence(t *testing.T) {
16511651

16521652
// Client verifies response - should fail
16531653
verifier1, err := NewP256Verifier("key20", *pub, NewVerifyConfig().SetVerifyCreated(false),
1654-
*NewFields().AddHeader("@status").AddHeaderExt("set-cookie", false, false, false))
1654+
*NewFields().AddHeader("@status").AddHeaderExt("set-cookie", false, false, false, false))
16551655
assert.NoError(t, err, "could not create verifier")
16561656
err = VerifyResponse("sig2", *verifier1, res, nil)
16571657
assert.Error(t, err, "binary sequence verified as non-bs")
16581658

16591659
// Client verifies response - should succeed
16601660
verifier2, err := NewP256Verifier("key20", *pub, NewVerifyConfig().SetVerifyCreated(false),
1661-
*NewFields().AddHeader("@status").AddHeaderExt("set-cookie", false, true, false))
1661+
*NewFields().AddHeader("@status").AddHeaderExt("set-cookie", false, true, false, false))
16621662
assert.NoError(t, err, "could not create verifier")
16631663
err = VerifyResponse("sig2", *verifier2, res, nil)
16641664
assert.NoError(t, err, "could not verify response")

trailer_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package httpsign
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
"net/url"
7+
"reflect"
8+
"testing"
9+
)
10+
11+
var rawPost1 = "POST /foo HTTP/1.1\nContent-Type: text/plain\nTransfer-Encoding: chunked\nTrailer: Expires\n\n4\nHTTP\r\n7\r\nMessage\r\na\r\nSignatures\r\n0\r\nExpires: Wed, 9 Nov 2022 07:28:00 GMT\r\n\r\n"
12+
13+
// This identical representation fails, see https://github.com/golang/go/issues/56835
14+
var rawPost2 = `POST /foo HTTP/1.1
15+
Content-Type: text/plain
16+
Transfer-Encoding: chunked
17+
Trailer: Expires
18+
19+
4
20+
HTTP
21+
7
22+
Message
23+
a
24+
Signatures
25+
0
26+
Expires: Wed, 9 Nov 2022 07:28:00 GMT
27+
28+
`
29+
30+
func TestTrailer_Get(t *testing.T) {
31+
ts := makeTestServer()
32+
defer ts.Close()
33+
34+
c := &Client{config: ClientConfig{
35+
signatureName: "sig1",
36+
signer: func() *Signer {
37+
signer, _ := NewHMACSHA256Signer("key1", bytes.Repeat([]byte{1}, 64), NewSignConfig(),
38+
Headers("@method", "hdr"))
39+
return signer
40+
}(),
41+
verifier: nil,
42+
fetchVerifier: nil,
43+
},
44+
client: *http.DefaultClient,
45+
}
46+
req := readRequest(rawPost1)
47+
48+
req.RequestURI = "" // otherwise Do will complain
49+
u, err := url.Parse(ts.URL + "/foo")
50+
if err != nil {
51+
panic(err)
52+
}
53+
req.URL = u
54+
55+
res, err := c.Do(req)
56+
var gotRes string
57+
if res != nil {
58+
gotRes = res.Status
59+
}
60+
if err != nil {
61+
t.Errorf("Get() error = %v", err)
62+
return
63+
}
64+
if !reflect.DeepEqual(gotRes, "200 OK") {
65+
t.Errorf("Get() gotRes = %v", gotRes)
66+
}
67+
68+
t.Errorf("No trailer support")
69+
}

0 commit comments

Comments
 (0)