fleet/third_party/httpsig-go/fz_test.go
Victor Lyuboslavsky c25fed2492
Added a vendored version of httpsig-go. (#30820)
For #30473

This change adds a vendored `httpsig-go` library to our repo. We cannot
use the upstream library because it has not merged the change we need:
https://github.com/remitly-oss/httpsig-go/pull/25

Thus, we need our own copy at this point.

The instructions for keeping this library up to date (if needed) are in
`UPDATE_INSTRUCTIONS`.

None of the coderabbitai review comments are relevant to the
code/features we are going to use for HTTP message signatures.

We will use this library in subsequent PRs for the TPM-backed HTTP
message signature feature.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Introduced a Go library for HTTP message signing and verification,
supporting multiple cryptographic algorithms (RSA, ECDSA, Ed25519,
HMAC).
* Added utilities for key management, including JWK and PEM key
handling.
* Provided HTTP client and server helpers for automatic request signing
and signature verification.
* Implemented structured error handling and metadata extraction for
signatures.

* **Documentation**
  * Added comprehensive README, usage examples, and update instructions.
* Included license and configuration files for third-party and testing
tools.

* **Tests**
* Added extensive unit, integration, and fuzz tests covering signing,
verification, and key handling.
* Included official RFC test vectors and various test data files for
robust validation.

* **Chores**
* Integrated continuous integration workflows and ignore files for code
quality and security analysis.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-14 20:26:50 +02:00

200 lines
4.3 KiB
Go

package httpsig
import (
"bufio"
"bytes"
"net/http"
"os"
"testing"
"github.com/remitly-oss/httpsig-go/sigtest"
)
// FuzzSigningOptions fuzzes the basic user input to SigningOptions
func FuzzSigningOptions1(f *testing.F) {
testcases := [][]string{
{"", "", "", ""},
{"", "0", "0", "\xde"},
{"", "\n", "0", "0"},
{"", "", "0", "@"},
{"", "@query-param", "0", "0"},
{string(Algo_ECDSA_P256_SHA256), "@query", "0", "0"},
{"any", "@query", "0", "0"},
{string(Algo_ED25519), "@query", "0", "0"},
}
for _, tc := range testcases {
f.Add(tc[0], tc[1], tc[2], tc[3])
}
reqtxt, err := os.ReadFile("testdata/rfc-test-request.txt")
if err != nil {
f.Fatal(err)
}
f.Fuzz(func(t *testing.T, algo, label, keyID, tag string) {
t.Logf("Label: %s\n", label)
t.Logf("keyid: %s\n", keyID)
t.Logf("tag: %s\n", tag)
fields := Fields(label, keyID, tag)
fields = append(fields, SignedField{
Name: label,
Parameters: map[string]any{
keyID: tag,
},
})
privKey := sigtest.ReadTestPrivateKey(t, "test-key-ed25519.key")
so := SigningProfile{
Algorithm: Algo_ED25519,
Fields: Fields(label, keyID, tag),
Metadata: []Metadata{MetaKeyID, MetaTag},
Label: label,
}
sk := SigningKey{
Key: privKey,
MetaKeyID: keyID,
MetaTag: tag,
}
if so.validate(sk) != nil {
// Catching invalidate signing options is good.
return
}
req, err := http.ReadRequest(bufio.NewReader(bytes.NewBuffer(reqtxt)))
if err != nil {
t.Fatal(err)
}
err = Sign(req, so, sk)
if err != nil {
if _, ok := err.(*SignatureError); ok {
// Handled error
return
}
// Unhandled error
t.Error(err)
}
})
}
func FuzzSigningOptionsFields(f *testing.F) {
testcases := [][]string{
{"", "", ""},
{"0", "0", "\xde"},
{"\n", "0", "0"},
{"", "0", "@"},
{"@query-param", "name", "0"},
{"@query", "0", "0"},
{"@method", "", ""},
{"@status", "", ""},
}
for _, tc := range testcases {
f.Add(tc[0], tc[1], tc[2])
}
reqtxt, err := os.ReadFile("testdata/rfc-test-request.txt")
if err != nil {
f.Fatal(err)
}
f.Fuzz(func(t *testing.T, field, tagName, tagValue string) {
t.Logf("field: %s\n", field)
t.Logf("tag: %s:%s\n", tagName, tagValue)
fields := []SignedField{}
if tagName == "" {
fields = append(fields, SignedField{
Name: field,
})
} else {
fields = append(fields, SignedField{
Name: field,
Parameters: map[string]any{
tagName: tagValue,
},
})
}
so := SigningProfile{
Algorithm: Algo_ED25519,
Fields: fields,
}
sk := SigningKey{
Key: sigtest.ReadTestPrivateKey(t, "test-key-ed25519.key"),
}
if so.validate(sk) != nil {
// Catching invalidate signing options is good.
return
}
req, err := http.ReadRequest(bufio.NewReader(bytes.NewBuffer(reqtxt)))
if err != nil {
t.Fatal(err)
}
err = Sign(req, so, sk)
if err != nil {
if _, ok := err.(*SignatureError); ok {
// Handled error
return
}
// Unhandled error
t.Error(err)
}
})
}
func FuzzExtractSignatures(f *testing.F) {
testcases := []struct {
SignatureHeader string
SignatureInputHeader string
}{
{
SignatureHeader: "",
SignatureInputHeader: "",
},
{
SignatureHeader: "sig-b24=(\"@status\" \"content-type\" \"content-digest\" \"content-length\");created=1618884473;keyid=\"test-key-ecc-p256\"",
SignatureInputHeader: "sig-b24=:wNmSUAhwb5LxtOtOpNa6W5xj067m5hFrj0XQ4fvpaCLx0NKocgPquLgyahnzDnDAUy5eCdlYUEkLIj+32oiasw==:",
},
}
for _, tc := range testcases {
f.Add(tc.SignatureHeader, tc.SignatureInputHeader)
}
reqtxt, err := os.ReadFile("testdata/rfc-test-request.txt")
if err != nil {
f.Fatal(err)
}
f.Fuzz(func(t *testing.T, sigHeader, sigInputHeader string) {
t.Logf("signature header: %s\n", sigHeader)
t.Logf("signature input header: %s\n", sigInputHeader)
req, err := http.ReadRequest(bufio.NewReader(bytes.NewBuffer(reqtxt)))
if err != nil {
t.Fatal(err)
}
req.Header.Set("signature", sigHeader)
req.Header.Set("signature-input", sigInputHeader)
sigSFV, err := parseSignaturesFromRequest(req.Header)
if err != nil {
return
}
for _, label := range sigSFV.Sigs.Names() {
_, err = unmarshalSignature(sigSFV, label)
if err != nil {
if _, ok := err.(*SignatureError); ok {
// Handled error
return
}
// Unhandled error
t.Error(err)
}
}
})
}