Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# See https://editorconfig.org

# top-most EditorConfig file
root = true

# Go files
[*._test.go]
# Preserve trailing whitespace in tests since some depend on it
trim_trailing_whitespace = false
42 changes: 39 additions & 3 deletions crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/stretchr/testify/assert"
"reflect"
"strings"
"testing"

"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/stretchr/testify/assert"
)

func TestNewHMACSHA256Signer(t *testing.T) {
Expand Down Expand Up @@ -162,6 +163,41 @@ func TestForeignSigner(t *testing.T) {
}
}

// Same as TestForeignSigner but using Message
func TestMessageForeignSigner(t *testing.T) {
priv, pub, err := genP256KeyPair()
if err != nil {
t.Errorf("Failed to generate keypair: %v", err)
}

config := NewSignConfig().setFakeCreated(1618884475).SignAlg(false)
signatureName := "sig1"
fields := *NewFields().AddHeader("@method").AddHeader("date").AddHeader("content-type").AddQueryParam("pet")
signer, err := NewJWSSigner(jwa.ES256, priv, config.SetKeyID("key1"), fields)
if err != nil {
t.Errorf("Failed to create JWS signer")
}
req := readRequest(httpreq2)
sigInput, sig, err := SignRequest(signatureName, *signer, req)
if err != nil {
t.Errorf("signature failed: %v", err)
}
req.Header.Add("Signature", sig)
req.Header.Add("Signature-Input", sigInput)
verifier, err := NewJWSVerifier(jwa.ES256, pub, NewVerifyConfig().SetVerifyCreated(false).SetKeyID("key1"), fields)
if err != nil {
t.Errorf("could not generate Verifier: %s", err)
}
msg, err := NewMessage(NewMessageConfig().WithRequest(req))
if err != nil {
t.Errorf("Failed to create Message")
}
_, err = msg.Verify(signatureName, *verifier)
if err != nil {
t.Errorf("verification error: %s", err)
}
}

func makeRSAPrivateKey() *rsa.PrivateKey {
priv, _ := rsa.GenerateKey(rand.Reader, 2048)
return priv
Expand Down
176 changes: 175 additions & 1 deletion fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package httpsign

import (
"encoding/base64"
"github.com/stretchr/testify/assert"
"net/http"
"net/url"
"testing"

"github.com/stretchr/testify/assert"
)

var httpreq1pssNoSig = `POST /foo?param=Value&Pet=dog HTTP/1.1
Expand Down Expand Up @@ -55,6 +58,50 @@ func FuzzVerifyRequest(f *testing.F) {
})
}

// Same as FuzzVerifyRequest but using Message
func FuzzMessageVerifyRequest(f *testing.F) {
type inputs struct {
req, sigInput, sig string
}
testcases := []inputs{
{httpreq1pssNoSig,
"sig-b21=();created=1618884473;keyid=\"test-key-rsa-pss\";nonce=\"b3k2pp5k7z-50gnwp.yemd\"",
"sig-b21=:d2pmTvmbncD3xQm8E9ZV2828BjQWGgiwAaw5bAkgibUopemLJcWDy/lkbbHAve4cRAtx31Iq786U7it++wgGxbtRxf8Udx7zFZsckzXaJMkA7ChG52eSkFxykJeNqsrWH5S+oxNFlD4dzVuwe8DhTSja8xxbR/Z2cOGdCbzR72rgFWhzx2VjBqJzsPLMIQKhO4DGezXehhWwE56YCE+O6c0mKZsfxVrogUvA4HELjVKWmAvtl6UnCh8jYzuVG5WSb/QEVPnP5TmcAnLH1g+s++v6d4s8m0gCw1fV5/SITLq9mhho8K3+7EPYTU8IU1bLhdxO5Nyt8C8ssinQ98Xw9Q==:",
},
{httpreq1pssNoSig,
"sig-b21=(date);created=1618884473;keyid=\"test-key-rsa-pss\";nonce=\"xxxb3k5k7z-50gnwp.yemd\"",
"sig-b21=:d2pmTvmbncD3xQm8E9ZV2828BjQWGgiwAaw5bAkgibUopemLJcWDy/lkbbHAve4cRAtx31Iq786U7it++wgGxbtRxf8Udx7zFZsckzXaJMkA7ChG52eSkFxykJeNqsrWH5S+oxNFlD4dzVuwe8DhTSja8xxbR/Z2cOGdCbzR72rgFWhzx2VjBqJzsPLMIQKhO4DGezXehhWwE56YCE+O6c0mKZsfxVrogUvA4HELjVKWmAvtl6UnCh8jYzuVG5WSb/QEVPnP5TmcAnLH1g+s++v6d4s8m0gCw1fV5/SITLq9mhho8K3+7EPYTU8IU1bLhdxO5Nyt8C8ssinQ98Xw9Q==:",
},
{httpreq1pssNoSig,
"sig-b21=(some-field;tr);created=1618884473;keyid=\"test-key-rsa-pss\";nonce=\"xxxb3k5k7z-50gnwp.yemd\"",
"sig-b21=:d2pmTvmbncD3xQm8E9ZV2828BjQWGgiwAaw5bAkgibUopemLJcWDy/lkbbHAve4cRAtx31Iq786U7it++wgGxbtRxf8Udx7zFZsckzXaJMkA7ChG52eSkFxykJeNqsrWH5S+oxNFlD4dzVuwe8DhTSja8xxbR/Z2cOGdCbzR72rgFWhzx2VjBqJzsPLMIQKhO4DGezXehhWwE56YCE+O6c0mKZsfxVrogUvA4HELjVKWmAvtl6UnCh8jYzuVG5WSb/QEVPnP5TmcAnLH1g+s++v6d4s8m0gCw1fV5/SITLq9mhho8K3+7EPYTU8IU1bLhdxO5Nyt8C8ssinQ98Xw9Q==:",
},
{httpreq1pssNoSig,
"sig-b22=(some-field;tr;bs);created=1618884473;keyid=\"test-key-rsa-pss\";nonce=\"xxxb3k5k7z-50gnwp.yemd\"",
"sig-b22=:d2pmTvmbncD3xQm8E9ZV2828BjQWGgiwAaw5bAkgibUopemLJcWDy/lkbbHAve4cRAtx31Iq786U7it++wgGxbtRxf8Udx7zFZsckzXaJMkA7ChG52eSkFxykJeNqsrWH5S+oxNFlD4dzVuwe8DhTSja8xxbR/Z2cOGdCbzR72rgFWhzx2VjBqJzsPLMIQKhO4DGezXehhWwE56YCE+O6c0mKZsfxVrogUvA4HELjVKWmAvtl6UnCh8jYzuVG5WSb/QEVPnP5TmcAnLH1g+s++v6d4s8m0gCw1fV5/SITLq9mhho8K3+7EPYTU8IU1bLhdxO5Nyt8C8ssinQ98Xw9Q==:",
},
}
for _, tc := range testcases {
f.Add(tc.req, tc.sigInput, tc.sig) // Use f.Add to provide a seed corpus
}
f.Fuzz(func(t *testing.T, reqString, sigInput, sig string) {
req := readRequest(reqString)
if req != nil {
req.Header.Set("Signature-Input", sigInput)
req.Header.Set("Signature", sig)
}

sigName := "sig-b21"
verifier := makeRSAVerifier(f, "key1", *NewFields())
msg, err := NewMessage(NewMessageConfig().WithRequest(req))
if err != nil {
t.Errorf("Failed to create Message")
}
_, _ = msg.Verify(sigName, verifier)
// only report panics
})
}

func FuzzSignAndVerifyHMAC(f *testing.F) {
type inputs struct {
req string
Expand Down Expand Up @@ -83,3 +130,130 @@ func FuzzSignAndVerifyHMAC(f *testing.F) {
}
})
}

// Same as FuzzSignAndVerifyHMAC but using Message
func FuzzMessageSignAndVerifyHMAC(f *testing.F) {
type inputs struct {
req string
}
testcases := []inputs{
{httpreq1},
}
for _, tc := range testcases {
f.Add(tc.req)
}
f.Fuzz(func(t *testing.T, reqString string) {
config := NewSignConfig().SignAlg(false).setFakeCreated(1618884475)
fields := Headers("@authority", "date", "content-type")
signatureName := "sig1"
key, _ := base64.StdEncoding.DecodeString("uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBtbmHhIDi6pcl8jsasjlTMtDQ==")
signer, _ := NewHMACSHA256Signer(key, config.SetKeyID("test-shared-secret"), fields)
req := readRequest(reqString)
sigInput, sig, err := SignRequest(signatureName, *signer, req)
if err == nil {
req.Header.Add("Signature", sig)
req.Header.Add("Signature-Input", sigInput)
verifier, err := NewHMACSHA256Verifier(key, NewVerifyConfig().SetVerifyCreated(false).SetKeyID("test-shared-secret"), fields)
assert.NoError(t, err, "could not generate Verifier")
msg, err := NewMessage(NewMessageConfig().WithRequest(req))
if err != nil {
t.Errorf("Failed to create Message")
}
_, err = msg.Verify(signatureName, *verifier)
assert.NoError(t, err, "verification error")
}
})
}

func FuzzMessageVerify(f *testing.F) {
f.Add("GET", "https://example.com/path", "example.com", "https", 0, "", "", "", "", true, false)
f.Add("POST", "https://api.example.com", "api.example.com", "https", 0, "", "", "", "", false, true)
f.Add("", "", "", "", 200, "GET", "https://example.com", "example.com", "https", true, false)
f.Add("PUT", "", "", "http", 0, "", "", "", "", false, false)
f.Add("", "", "", "", 404, "", "", "", "", false, false)
f.Add("0", "%", "0", "0", 0, "", "", "", "", true, false)

f.Fuzz(func(t *testing.T, method, urlStr, authority, scheme string, statusCode int,
assocMethod, assocURLStr, assocAuthority, assocScheme string,
hasHeaders, hasTrailers bool) {

config := NewMessageConfig()

if method != "" {
config = config.WithMethod(method)
}
if urlStr != "" {
u, err := url.Parse(urlStr)
if err == nil {
config = config.WithURL(u)
}
}
if authority != "" {
config = config.WithAuthority(authority)
}
if scheme != "" {
config = config.WithScheme(scheme)
}

if statusCode > 0 {
config = config.WithStatusCode(statusCode)
}

if hasHeaders {
headers := http.Header{
"Content-Type": []string{"application/json"},
"X-Test": []string{"fuzz"},
}
config = config.WithHeaders(headers)
}
if hasTrailers {
trailers := http.Header{
"X-Trailer": []string{"test"},
}
config = config.WithTrailers(trailers)
}

if statusCode > 0 && assocMethod != "" {
var assocURL *url.URL
if assocURLStr != "" {
assocURL, _ = url.Parse(assocURLStr)
}
assocHeaders := http.Header{"X-Assoc": []string{"test"}}
config = config.WithAssociatedRequest(assocMethod, assocURL, assocHeaders, assocAuthority, assocScheme)
}

msg, err := NewMessage(config)

if err == nil {
if msg.headers == nil && msg.method != "" {
t.Errorf("Request message created without headers")
}
if msg.headers == nil && msg.statusCode != nil {
t.Errorf("Response message created without headers")
}

key, _ := base64.StdEncoding.DecodeString("uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBtbmHhIDi6pcl8jsasjlTMtDQ==")
verifier, _ := NewHMACSHA256Verifier(key, NewVerifyConfig().SetVerifyCreated(false), Fields{})

if msg.headers != nil {
msg.headers.Set("Signature-Input", `sig1=("@method");created=1618884473;keyid="test-key"`)
msg.headers.Set("Signature", `sig1=:test:`)
}

_, _ = msg.Verify("sig1", *verifier)
}

if err != nil {
hasRequest := method != ""
hasResponse := statusCode > 0

if !hasRequest && !hasResponse {
assert.Contains(t, err.Error(), "must have either method")
} else if hasRequest && hasResponse {
assert.Contains(t, err.Error(), "cannot have both request and response")
} else if (hasRequest || hasResponse) && !hasHeaders {
assert.Contains(t, err.Error(), "must have headers")
}
}
})
}
60 changes: 59 additions & 1 deletion http2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import (
"bufio"
"bytes"
"crypto/tls"
"github.com/andreyvit/diff"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"text/template"

"github.com/andreyvit/diff"
)

var wantFields = `"kuku": my awesome header
Expand Down Expand Up @@ -96,6 +97,61 @@ func testHTTP(t *testing.T, proto string) {
simpleClient(t, proto, simpleHandler)
}

func testMessageHTTP(t *testing.T, proto string) {
simpleHandler := func(w http.ResponseWriter, r *http.Request) {
reqProto := r.Proto
if reqProto != proto {
t.Errorf("expected %s, got %s", proto, reqProto)
}
var scheme string
if ts.TLS == nil {
scheme = "http"
} else {
scheme = "https"
}
sp := bytes.Split([]byte(ts.URL), []byte(":"))
portval, err := strconv.Atoi(string(sp[2]))
if err != nil {
t.Errorf("cannot parse server port number")
}
tpl, err := template.New("fields").Parse(wantFields)
if err != nil {
t.Errorf("could not parse template")
}
type inputs struct {
Port int
Scheme string
}
// Use the Template facility to create the list of expected signed fields
wf, err := execTemplate(*tpl, "fields", inputs{Port: portval, Scheme: scheme})
if err != nil {
t.Errorf("execTemplate failed")
}
verifier, err := NewHMACSHA256Verifier(bytes.Repeat([]byte{0x03}, 64),
NewVerifyConfig().SetVerifyCreated(false).SetKeyID("key1"),
Headers("@query"))
if err != nil {
t.Errorf("could not create verifier")
}
msg, err := NewMessage(NewMessageConfig().WithRequest(r))
if err != nil {
t.Errorf("could not create message")
}
sigInput, _, err := verifyDebug("sig1", *verifier, msg)
if err != nil {
t.Errorf("failed to verify request: sig input: %s\nerr: %v", sigInput, err)
}

if sigInput != wf {
t.Errorf("unexpected fields: %s\n", diff.CharacterDiff(sigInput, wantFields))
}
w.WriteHeader(200)
}

// And run the client code...
simpleClient(t, proto, simpleHandler)
}

func simpleClient(t *testing.T, proto string, simpleHandler func(w http.ResponseWriter, r *http.Request)) {
// Client code
switch proto {
Expand Down Expand Up @@ -153,8 +209,10 @@ func simpleClient(t *testing.T, proto string, simpleHandler func(w http.Response

func TestHTTP11(t *testing.T) {
testHTTP(t, "HTTP/1.1")
testMessageHTTP(t, "HTTP/1.1")
}

func TestHTTP20(t *testing.T) {
testHTTP(t, "HTTP/2.0")
testMessageHTTP(t, "HTTP/2.0")
}
Loading