Skip to content

Commit 09ffb81

Browse files
committed
No alg parameter for JWS, and verify Date header
1 parent 6451ace commit 09ffb81

File tree

9 files changed

+73
-44
lines changed

9 files changed

+73
-44
lines changed

config.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ type VerifyConfig struct {
7979
rejectExpired bool
8080
requestResponse *requestResponse
8181
verifyKeyID bool
82+
dateWithin time.Duration
8283
}
8384

8485
// SetNotNewerThan sets the window for messages that appear to be newer than the current time,
@@ -136,6 +137,15 @@ func (v *VerifyConfig) SetVerifyKeyID(verify bool) *VerifyConfig {
136137
return v
137138
}
138139

140+
// SetVerifyDateWithin indicates that the Date header should be verified if it exists, and its value
141+
// must be within a certain time duration (positive or negative) of the Created signature parameter.
142+
// This verification is only available if the Created field itself is verified.
143+
// Default: 0, meaning no verification of the Date header.
144+
func (v *VerifyConfig) SetVerifyDateWithin(d time.Duration) *VerifyConfig {
145+
v.dateWithin = d
146+
return v
147+
}
148+
139149
// NewVerifyConfig generates a default configuration.
140150
func NewVerifyConfig() *VerifyConfig {
141151
return &VerifyConfig{
@@ -145,6 +155,7 @@ func NewVerifyConfig() *VerifyConfig {
145155
rejectExpired: true,
146156
allowedAlgs: []string{},
147157
verifyKeyID: true,
158+
dateWithin: 0, // meaning no constraint
148159
}
149160
}
150161

crypto.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func NewJWSSigner(alg jwa.SignatureAlgorithm, keyID string, key interface{}, con
149149
return &Signer{
150150
keyID: keyID,
151151
key: key,
152-
alg: string(alg),
152+
alg: "",
153153
config: config,
154154
fields: fields,
155155
foreignSigner: jwsSigner,
@@ -332,7 +332,7 @@ func NewJWSVerifier(alg jwa.SignatureAlgorithm, key interface{}, keyID string, c
332332
return &Verifier{
333333
keyID: keyID,
334334
key: key,
335-
alg: string(alg),
335+
alg: "",
336336
config: config,
337337
fields: fields,
338338
foreignVerifier: verifier,

crypto_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ func TestNewJWSVerifier(t *testing.T) {
279279
want: &Verifier{
280280
keyID: "key200",
281281
key: "1234",
282-
alg: "HS256",
282+
alg: "",
283283
config: NewVerifyConfig(),
284284
fields: *NewFields(),
285285
foreignVerifier: verifier,

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ require (
66
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
77
github.com/dunglas/httpsfv v0.1.1
88
github.com/lestrrat-go/jwx v1.2.18
9+
github.com/stretchr/testify v1.7.0
910
)
1011

1112
require (
13+
github.com/davecgh/go-spew v1.1.1 // indirect
1214
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect
1315
github.com/goccy/go-json v0.9.4 // indirect
1416
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
@@ -17,6 +19,8 @@ require (
1719
github.com/lestrrat-go/iter v1.0.1 // indirect
1820
github.com/lestrrat-go/option v1.0.0 // indirect
1921
github.com/pkg/errors v0.9.1 // indirect
22+
github.com/pmezard/go-difflib v1.0.0 // indirect
2023
github.com/sergi/go-diff v1.2.0 // indirect
2124
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 // indirect
25+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
2226
)

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ github.com/dunglas/httpsfv v0.1.1 h1:iV2PWNlj9Qbk+5I3fxPDJPTh9eM0rskrI1qdUVUNK1E
1010
github.com/dunglas/httpsfv v0.1.1/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
1111
github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI=
1212
github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
13+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
1314
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
1415
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
16+
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
1517
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
1618
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
1719
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
@@ -46,6 +48,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
4648
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
4749
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
4850
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
51+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
4952
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5053
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
5154
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

handler_test.go

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package httpsign
33
import (
44
"bytes"
55
"fmt"
6+
"github.com/stretchr/testify/assert"
67
"io"
78
"log"
89
"net/http"
@@ -38,29 +39,19 @@ func Test_WrapHandler(t *testing.T) {
3839

3940
signer, err := NewHMACSHA256Signer("key", bytes.Repeat([]byte{1}, 64), nil,
4041
Headers("@method"))
41-
if err != nil {
42-
t.Errorf("%v", err)
43-
}
42+
assert.NoError(t, err)
4443

4544
verifier, err := NewHMACSHA256Verifier("key", bytes.Repeat([]byte{0}, 64), NewVerifyConfig(), *NewFields())
46-
if err != nil {
47-
t.Errorf("%v", err)
48-
}
45+
assert.NoError(t, err)
4946
client := NewDefaultClient("sig1", signer, verifier, nil)
5047
res, err := client.Get(ts.URL)
51-
if err != nil {
52-
t.Errorf("%v", err)
53-
}
48+
assert.NoError(t, err)
5449
if res != nil {
5550
_, err = io.ReadAll(res.Body)
5651
_ = res.Body.Close()
57-
if err != nil {
58-
t.Errorf("%v", err)
59-
}
52+
assert.NoError(t, err)
6053

61-
if res.Status != "200 OK" {
62-
t.Errorf("Bad status returned")
63-
}
54+
assert.Equal(t, res.Status, "200 OK", "Bad status returned")
6455
}
6556
}
6657

@@ -199,11 +190,7 @@ func TestWrapHandlerServerFails(t *testing.T) { // non-default verify handler
199190

200191
// Send an HTTP GET, get response -- signing and verification happen behind the scenes
201192
res, err := client.Get(ts.URL)
202-
if err != nil {
203-
t.Errorf("Get failed: %s", err)
204-
}
193+
assert.NoError(t, err, "Get failed")
205194

206-
if res.StatusCode != 599 {
207-
t.Errorf("Verification did not fail?")
208-
}
195+
assert.Equal(t, res.StatusCode, 599, "Verification did not fail?")
209196
}

httpparse.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ func parseResponse(res *http.Response) (*parsedMessage, error) {
5555
return nil, err
5656
}
5757

58-
return &parsedMessage{derived: generateResDerivedComponents(res), url: nil, headers: normalizeHeaderNames(res.Header)}, nil
58+
return &parsedMessage{derived: generateResDerivedComponents(res), url: nil,
59+
headers: normalizeHeaderNames(res.Header)}, nil
5960
}
6061

6162
func validateMessageHeaders(header http.Header) error {

signatures.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ func verifyMessage(config VerifyConfig, name string, verifier Verifier, message
389389
if !(psiSig.fields.contains(&fields)) {
390390
return "", fmt.Errorf("actual signature does not cover all required fields")
391391
}
392-
err = applyVerificationPolicy(verifier, psiSig, config)
392+
err = applyVerificationPolicy(verifier, message, psiSig, config)
393393
if err != nil {
394394
return "", err
395395
}
@@ -400,8 +400,8 @@ func verifyMessage(config VerifyConfig, name string, verifier Verifier, message
400400
return signatureInput, verifySignature(verifier, signatureInput, wantSigRaw)
401401
}
402402

403-
func applyVerificationPolicy(verifier Verifier, psi *psiSignature, config VerifyConfig) error {
404-
err := applyPolicyCreated(psi, config)
403+
func applyVerificationPolicy(verifier Verifier, message parsedMessage, psi *psiSignature, config VerifyConfig) error {
404+
err := applyPolicyCreated(psi, message, config)
405405
if err != nil {
406406
return err
407407
}
@@ -477,7 +477,10 @@ func applyPolicyAlgs(psi *psiSignature, config VerifyConfig) error {
477477
return nil
478478
}
479479

480-
func applyPolicyCreated(psi *psiSignature, config VerifyConfig) error {
480+
func applyPolicyCreated(psi *psiSignature, message parsedMessage, config VerifyConfig) error {
481+
if !config.verifyCreated && config.dateWithin != 0 {
482+
return fmt.Errorf("cannot verify Date header if Created parameter is not verified")
483+
}
481484
if config.verifyCreated {
482485
now := time.Now()
483486
createdParam, ok := psi.params["created"]
@@ -495,6 +498,23 @@ func applyPolicyCreated(psi *psiSignature, config VerifyConfig) error {
495498
if createdTime.Add(config.notOlderThan).Before(now) {
496499
return fmt.Errorf("message is too old, check for replay")
497500
}
501+
502+
if config.dateWithin != 0 {
503+
dateHdr, ok := message.headers["date"]
504+
if ok {
505+
if len(dateHdr) > 1 {
506+
return fmt.Errorf("multiple Date headers?")
507+
}
508+
date, err := http.ParseTime(dateHdr[0])
509+
if err != nil {
510+
return fmt.Errorf("cannot parse Date header: %w", err)
511+
}
512+
if createdTime.After(date.Add(config.dateWithin)) ||
513+
date.After(createdTime.Add(config.dateWithin)) {
514+
return fmt.Errorf("the Date header is not within time window of Created parameter")
515+
}
516+
}
517+
}
498518
}
499519
return nil
500520
}

signatures_test.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"encoding/base64"
1515
"encoding/pem"
1616
"fmt"
17+
"github.com/stretchr/testify/assert"
1718
"net/http"
1819
"strings"
1920
"testing"
@@ -549,9 +550,7 @@ func TestSignRequest(t *testing.T) {
549550

550551
func makeRSAPSSSigner(t *testing.T, config SignConfig, fields Fields) Signer {
551552
prvKey, err := loadRSAPSSPrivateKey(rsaPSSPrvKey)
552-
if err != nil {
553-
t.Errorf("cannot parse private key: %v", err)
554-
}
553+
assert.NoError(t, err, "cannot parse private key")
555554
signer, _ := NewRSAPSSSigner("test-key-rsa-pss", *prvKey, &config, fields)
556555
return *signer
557556
}
@@ -637,13 +636,9 @@ func TestSignAndVerifyHMAC(t *testing.T) {
637636
req.Header.Add("Signature", sig)
638637
req.Header.Add("Signature-Input", sigInput)
639638
verifier, err := NewHMACSHA256Verifier("test-shared-secret", key, NewVerifyConfig().SetVerifyCreated(false), fields)
640-
if err != nil {
641-
t.Errorf("could not generate Verifier: %s", err)
642-
}
639+
assert.NoError(t, err, "could not generate Verifier")
643640
err = VerifyRequest(signatureName, *verifier, req)
644-
if err != nil {
645-
t.Errorf("verification error: %s", err)
646-
}
641+
assert.NoError(t, err, "verification error")
647642
}
648643

649644
func TestSignAndVerifyHMACBad(t *testing.T) {
@@ -658,13 +653,9 @@ func TestSignAndVerifyHMACBad(t *testing.T) {
658653
req.Header.Add("Signature-Input", sigInput)
659654
badkey := append(key, byte(0x77))
660655
verifier, err := NewHMACSHA256Verifier("test-shared-secret", badkey, NewVerifyConfig().SetVerifyCreated(false), fields)
661-
if err != nil {
662-
t.Errorf("could not generate Verifier: %s", err)
663-
}
656+
assert.NoError(t, err, "could not generate Verifier")
664657
err = VerifyRequest(signatureName, *verifier, req)
665-
if err == nil {
666-
t.Errorf("verification should have failed")
667-
}
658+
assert.Error(t, err, "verification should have failed")
668659
}
669660

670661
func TestCreated(t *testing.T) {
@@ -675,16 +666,20 @@ func TestCreated(t *testing.T) {
675666
signConfig := NewSignConfig().SignCreated(true).setFakeCreated(createdTime)
676667
signer, _ := NewHMACSHA256Signer("test-shared-secret", key, signConfig, fields)
677668
res := readResponse(httpres2)
669+
nowStr := time.Now().UTC().Format(http.TimeFormat)
670+
res.Header.Set("Date", nowStr)
678671
sigInput, sig, _ := SignResponse(signatureName, *signer, res)
679672

680673
res2 := readResponse(httpres2)
674+
res2.Header.Set("Date", nowStr)
681675
res2.Header.Add("Signature", sig)
682676
res2.Header.Add("Signature-Input", sigInput)
683677
verifier, err := NewHMACSHA256Verifier("test-shared-secret", key, verifyConfig, fields)
684678
if err != nil {
685679
t.Errorf("could not generate Verifier: %s", err)
686680
}
687681
err = VerifyResponse(signatureName, *verifier, res2)
682+
688683
if wantSuccess && err != nil {
689684
t.Errorf("verification error: %s", err)
690685
}
@@ -711,13 +706,21 @@ func TestCreated(t *testing.T) {
711706
testNewWindow2 := func(t *testing.T) {
712707
testOnceWithConfig(t, now+15_000, NewVerifyConfig().SetNotNewerThan(14_000*time.Second), false)
713708
}
709+
testDate := func(t *testing.T) {
710+
testOnceWithConfig(t, now, NewVerifyConfig().SetVerifyDateWithin(100*time.Millisecond), true)
711+
}
712+
testDateFail := func(t *testing.T) {
713+
testOnceWithConfig(t, now, NewVerifyConfig().SetVerifyCreated(false).SetVerifyDateWithin(100*time.Millisecond), false)
714+
}
714715
t.Run("in window", testInWindow)
715716
t.Run("older", testOlder)
716717
t.Run("newer", testNewer)
717718
t.Run("older, smaller than window", testOldWindow1)
718719
t.Run("older, larger than window", testOldWindow2)
719720
t.Run("newer, smaller than window", testNewWindow1)
720721
t.Run("newer, larger than window", testNewWindow2)
722+
t.Run("verify Date header within window", testDate)
723+
t.Run("verify logic requires to verify Created", testDateFail)
721724
}
722725

723726
func TestSignAndVerifyResponseHMAC(t *testing.T) {

0 commit comments

Comments
 (0)