mirror of
https://github.com/fleetdm/fleet
synced 2026-05-06 06:48:54 +00:00
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 -->
217 lines
6.8 KiB
Go
217 lines
6.8 KiB
Go
package httpsig_test
|
|
|
|
import (
|
|
"crypto"
|
|
"testing"
|
|
|
|
"github.com/remitly-oss/httpsig-go"
|
|
"github.com/remitly-oss/httpsig-go/keyman"
|
|
"github.com/remitly-oss/httpsig-go/keyutil"
|
|
"github.com/remitly-oss/httpsig-go/sigtest"
|
|
)
|
|
|
|
// TestRoundTrip tests that the signing code can be verified by the verify code.
|
|
func TestRoundTrip(t *testing.T) {
|
|
|
|
testcases := []struct {
|
|
Name string
|
|
PrivateKey crypto.PrivateKey
|
|
MetaKeyID string
|
|
Secret []byte
|
|
SignProfile httpsig.SigningProfile
|
|
RequestFile string
|
|
Keys httpsig.KeyFetcher
|
|
Profile httpsig.VerifyProfile
|
|
ExpectedErrCodeVerify httpsig.ErrCode
|
|
}{
|
|
{
|
|
Name: "RSA-PSS",
|
|
PrivateKey: keyutil.MustReadPrivateKeyFile("testdata/test-key-rsa-pss.key"),
|
|
MetaKeyID: "test-key-rsa",
|
|
SignProfile: httpsig.SigningProfile{
|
|
Algorithm: httpsig.Algo_RSA_PSS_SHA512,
|
|
Fields: httpsig.DefaultRequiredFields,
|
|
Metadata: []httpsig.Metadata{httpsig.MetaCreated, httpsig.MetaKeyID},
|
|
Label: "tst-rsa-pss",
|
|
},
|
|
RequestFile: "rfc-test-request.txt",
|
|
Keys: keyman.NewKeyFetchInMemory(map[string]httpsig.KeySpec{
|
|
"test-key-rsa": {
|
|
KeyID: "test-key-rsa",
|
|
Algo: httpsig.Algo_RSA_PSS_SHA512,
|
|
PubKey: keyutil.MustReadPublicKeyFile("testdata/test-key-rsa-pss.pub"),
|
|
},
|
|
}),
|
|
Profile: createVerifyProfile("tst-rsa-pss"),
|
|
},
|
|
{
|
|
Name: "RSA-v15",
|
|
PrivateKey: keyutil.MustReadPrivateKeyFile("testdata/key-rsa-v15.key"),
|
|
MetaKeyID: "test-key-rsa",
|
|
SignProfile: httpsig.SigningProfile{
|
|
Algorithm: httpsig.Algo_RSA_v1_5_sha256,
|
|
Fields: httpsig.DefaultRequiredFields,
|
|
Metadata: []httpsig.Metadata{httpsig.MetaCreated, httpsig.MetaKeyID},
|
|
Label: "tst-rsa-pss",
|
|
},
|
|
RequestFile: "rfc-test-request.txt",
|
|
Keys: keyman.NewKeyFetchInMemory(map[string]httpsig.KeySpec{
|
|
"test-key-rsa": {
|
|
KeyID: "test-key-rsa",
|
|
Algo: httpsig.Algo_RSA_v1_5_sha256,
|
|
PubKey: keyutil.MustReadPublicKeyFile("testdata/key-rsa-v15.pub"),
|
|
},
|
|
}),
|
|
Profile: createVerifyProfile("tst-rsa-pss"),
|
|
},
|
|
{
|
|
Name: "HMAC_SHA256",
|
|
Secret: sigtest.MustReadFile("testdata/test-shared-secret"),
|
|
MetaKeyID: "test-key-shared",
|
|
SignProfile: httpsig.SigningProfile{
|
|
Algorithm: httpsig.Algo_HMAC_SHA256,
|
|
Fields: httpsig.DefaultRequiredFields,
|
|
Metadata: []httpsig.Metadata{httpsig.MetaCreated, httpsig.MetaKeyID},
|
|
},
|
|
RequestFile: "rfc-test-request.txt",
|
|
Keys: keyman.NewKeyFetchInMemory(map[string]httpsig.KeySpec{
|
|
"test-key-shared": {
|
|
KeyID: "test-key-shared",
|
|
Algo: httpsig.Algo_HMAC_SHA256,
|
|
Secret: sigtest.MustReadFile("testdata/test-shared-secret"),
|
|
},
|
|
}),
|
|
Profile: createVerifyProfile("sig1"),
|
|
},
|
|
{
|
|
Name: "ECDSA-p265",
|
|
PrivateKey: keyutil.MustReadPrivateKeyFile("testdata/test-key-ecc-p256.key"),
|
|
MetaKeyID: "test-key-ecdsa",
|
|
SignProfile: httpsig.SigningProfile{
|
|
Algorithm: httpsig.Algo_ECDSA_P256_SHA256,
|
|
Fields: httpsig.DefaultRequiredFields,
|
|
Metadata: []httpsig.Metadata{httpsig.MetaCreated, httpsig.MetaKeyID},
|
|
Label: "tst-ecdsa",
|
|
},
|
|
RequestFile: "rfc-test-request.txt",
|
|
Keys: keyman.NewKeyFetchInMemory(map[string]httpsig.KeySpec{
|
|
"test-key-ecdsa": {
|
|
KeyID: "test-key-ecds",
|
|
Algo: httpsig.Algo_ECDSA_P256_SHA256,
|
|
PubKey: keyutil.MustReadPublicKeyFile("testdata/test-key-ecc-p256.pub"),
|
|
},
|
|
}),
|
|
Profile: createVerifyProfile("tst-ecdsa"),
|
|
},
|
|
{
|
|
Name: "ECDSA-p384",
|
|
PrivateKey: keyutil.MustReadPrivateKeyFile("testdata/test-key-ecc-p384.key"),
|
|
MetaKeyID: "test-key-ecdsa",
|
|
SignProfile: httpsig.SigningProfile{
|
|
Algorithm: httpsig.Algo_ECDSA_P384_SHA384,
|
|
Fields: httpsig.DefaultRequiredFields,
|
|
Metadata: []httpsig.Metadata{httpsig.MetaCreated, httpsig.MetaKeyID},
|
|
Label: "tst-ecdsa",
|
|
},
|
|
RequestFile: "rfc-test-request.txt",
|
|
Keys: keyman.NewKeyFetchInMemory(map[string]httpsig.KeySpec{
|
|
"test-key-ecdsa": {
|
|
KeyID: "test-key-ecdsa",
|
|
Algo: httpsig.Algo_ECDSA_P384_SHA384,
|
|
PubKey: keyutil.MustReadPublicKeyFile("testdata/test-key-ecc-p384.pub"),
|
|
},
|
|
}),
|
|
Profile: createVerifyProfile("tst-ecdsa"),
|
|
},
|
|
{
|
|
Name: "ED25519",
|
|
PrivateKey: keyutil.MustReadPrivateKeyFile("testdata/test-key-ed25519.key"),
|
|
MetaKeyID: "test-key-ed",
|
|
SignProfile: httpsig.SigningProfile{
|
|
Algorithm: httpsig.Algo_ED25519,
|
|
Fields: httpsig.DefaultRequiredFields,
|
|
Metadata: []httpsig.Metadata{httpsig.MetaCreated, httpsig.MetaKeyID},
|
|
Label: "tst-ed",
|
|
},
|
|
RequestFile: "rfc-test-request.txt",
|
|
Keys: keyman.NewKeyFetchInMemory(map[string]httpsig.KeySpec{
|
|
"test-key-ed": {
|
|
KeyID: "test-key-ed",
|
|
Algo: httpsig.Algo_ED25519,
|
|
PubKey: keyutil.MustReadPublicKeyFile("testdata/test-key-ed25519.pub"),
|
|
},
|
|
}),
|
|
Profile: createVerifyProfile("tst-ed"),
|
|
},
|
|
{
|
|
Name: "BadDigest",
|
|
PrivateKey: keyutil.MustReadPrivateKeyFile("testdata/test-key-ed25519.key"),
|
|
MetaKeyID: "test-key-ed",
|
|
SignProfile: httpsig.SigningProfile{
|
|
|
|
Algorithm: httpsig.Algo_ED25519,
|
|
Fields: httpsig.DefaultRequiredFields,
|
|
Metadata: []httpsig.Metadata{httpsig.MetaCreated, httpsig.MetaKeyID},
|
|
Label: "tst-content-digest",
|
|
},
|
|
RequestFile: "request_bad_digest.txt",
|
|
Keys: keyman.NewKeyFetchInMemory(map[string]httpsig.KeySpec{
|
|
"test-key-ed": {
|
|
KeyID: "test-key-ed",
|
|
Algo: httpsig.Algo_ED25519,
|
|
PubKey: keyutil.MustReadPublicKeyFile("testdata/test-key-ed25519.pub"),
|
|
},
|
|
}),
|
|
Profile: createVerifyProfile("tst-content-digest"),
|
|
ExpectedErrCodeVerify: httpsig.ErrNoSigWrongDigest,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
var signer *httpsig.Signer
|
|
sk := httpsig.SigningKey{
|
|
Key: tc.PrivateKey,
|
|
Secret: tc.Secret,
|
|
MetaKeyID: tc.MetaKeyID,
|
|
}
|
|
|
|
signer, err := httpsig.NewSigner(tc.SignProfile, sk)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
req := sigtest.ReadRequest(t, tc.RequestFile)
|
|
err = signer.Sign(req)
|
|
if err != nil {
|
|
t.Fatalf("%#v", err)
|
|
}
|
|
t.Log(req.Header.Get("Signature-Input"))
|
|
t.Log(req.Header.Get("Signature"))
|
|
ver, err := httpsig.NewVerifier(tc.Keys, tc.Profile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
vf, err := ver.Verify(req)
|
|
if err != nil {
|
|
if tc.ExpectedErrCodeVerify != "" {
|
|
if sigerr, ok := err.(*httpsig.SignatureError); ok {
|
|
sigtest.Diff(t, tc.ExpectedErrCodeVerify, sigerr.Code, "Wrong err code")
|
|
}
|
|
} else {
|
|
t.Fatalf("%#v", err)
|
|
}
|
|
} else if tc.ExpectedErrCodeVerify != "" {
|
|
t.Fatal("Expected error")
|
|
}
|
|
t.Logf("%+v\n", vf)
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
func createVerifyProfile(label string) httpsig.VerifyProfile {
|
|
vp := httpsig.DefaultVerifyProfile
|
|
vp.SignatureLabel = label
|
|
return vp
|
|
}
|