mirror of
https://github.com/fleetdm/fleet
synced 2026-05-06 14:58:33 +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 -->
102 lines
2.8 KiB
Go
102 lines
2.8 KiB
Go
package httpsig
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
)
|
|
|
|
type contextKey int
|
|
|
|
const verifyKey contextKey = 0
|
|
|
|
// NewSigningHTTPClient creates an *http.Client that signs requests before sending. If hc is nil a new *http.Client is created. If signer is not nil all requests will be signed. If verifier is not nil all requests will be verified.
|
|
func NewHTTPClient(hc *http.Client, signer *Signer, verifier *Verifier) *http.Client {
|
|
if hc == nil {
|
|
hc = &http.Client{}
|
|
}
|
|
|
|
hc.Transport = NewTransport(hc.Transport, signer, verifier)
|
|
return hc
|
|
}
|
|
|
|
// NewTransport returns an http.RoundTripper implementation that signs requests and verifies responses if signer and verifier are not nil. If rt is nil http.DefaultTransport is used.
|
|
func NewTransport(rt http.RoundTripper, signer *Signer, verifier *Verifier) http.RoundTripper {
|
|
if rt == nil {
|
|
rt = http.DefaultTransport
|
|
}
|
|
|
|
return &transport{
|
|
sign: signer != nil,
|
|
signer: signer,
|
|
verify: verifier != nil,
|
|
verifier: verifier,
|
|
rt: rt,
|
|
}
|
|
}
|
|
|
|
// VerifyHandler verifies the http signature of each request. If not verified it returns a 401 Unauthorized HTTP error. If verified it puts the verification result in the requests context. Use GetVerifyResult to read the context.
|
|
type VerifyHandler struct {
|
|
handler http.Handler
|
|
verifier *Verifier
|
|
}
|
|
|
|
// NewHandler wraps an http.Handler with a an http.Handler that verifies each request. verifier cannot be nil.
|
|
func NewHandler(h http.Handler, verifier *Verifier) http.Handler {
|
|
if verifier == nil {
|
|
panic("verifier cannot be nil")
|
|
}
|
|
return &VerifyHandler{
|
|
handler: h,
|
|
verifier: verifier,
|
|
}
|
|
}
|
|
|
|
func (vh VerifyHandler) ServeHTTP(rw http.ResponseWriter, inReq *http.Request) {
|
|
vr, err := vh.verifier.Verify(inReq)
|
|
if err != nil {
|
|
// Failed to verify
|
|
rw.Write([]byte("Unauthorized"))
|
|
rw.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
req := inReq.WithContext(context.WithValue(inReq.Context(), verifyKey, &vr))
|
|
vh.handler.ServeHTTP(rw, req)
|
|
}
|
|
|
|
// GetVerifyResult returns the results of a successful request signature verification.
|
|
func GetVerifyResult(ctx context.Context) (v VerifyResult, found bool) {
|
|
if vr, ok := ctx.Value(verifyKey).(*VerifyResult); ok && vr != nil {
|
|
return *vr, true
|
|
}
|
|
return VerifyResult{}, false
|
|
}
|
|
|
|
// transport implements http.RoundTripper interface. It signs the request before calling the underlying RoundTripper
|
|
type transport struct {
|
|
sign bool
|
|
verify bool
|
|
signer *Signer
|
|
verifier *Verifier
|
|
rt http.RoundTripper
|
|
}
|
|
|
|
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
if t.sign {
|
|
// Signing does not read or close the body
|
|
err := t.signer.Sign(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
resp, err := t.rt.RoundTrip(req)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
if t.verify {
|
|
// Verifying does not read or close the response body
|
|
_, err = t.verifier.VerifyResponse(resp)
|
|
}
|
|
return resp, err
|
|
}
|