fleet/third_party/httpsig-go/http.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

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
}