fleet/third_party/httpsig-go/http.go

103 lines
2.8 KiB
Go
Raw Normal View History

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 18:26:50 +00:00
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
}