httpsig

package module
v0.0.0-...-ad3dc6e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2024 License: MIT Imports: 21 Imported by: 0

README

HTTP Message Signatures

Go Reference Go Report Card

An implementation of HTTP Message Signatures from RFC 9421.

HTTP signaturs are a mechanism for signing and verifying HTTP requests and responses.

Supported Features

The full specification is supported with the exception of the following. File a ticket or PR and support will be added Planned but not currently supported features:

  • JWS algorithms
  • Header parameters including trailers

net/http integration

Create net/http clients that sign requests and/or verifies repsonses.

	params := httpsig.SigningOptions{
		PrivateKey: nil, // Fill in your private key
		Algorithm:  httpsig.Algo_ECDSA_P256_SHA256,
		Fields:     httpsig.DefaultRequiredFields,
		Metadata:   []httpsig.Metadata{httpsig.MetaKeyID},
		MetaKeyID:  "key123",
	}

	// Create the signature signer
	signer, _ := httpsig.NewSigner(params)

	// Create a net/http Client that signs all requests
	signingClient := httpsig.NewHTTPClient(nil, signer, nil)

Create net/http Handlers that verify incoming requests to the server.

	myhandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Lookup the results of verification
		if veriftyResult, ok := httpsig.GetVerifyResult(r.Context()); ok {
			keyid, _ := veriftyResult.KeyID()
			fmt.Fprintf(w, "Hello, %s", keyid)
		} else {
			fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
		}
	})

	// Create a verifier
	verifier, _ := httpsig.NewVerifier(nil, httpsig.DefaultVerifyProfile)

	mux := http.NewServeMux()
	// Wrap the handler with the a signature verification handler.
	mux.Handle("/", httpsig.NewHandler(myhandler, verifier))

Stability

The public interface may change slightly before the v1 release.

References

Documentation

Index

Examples

Constants

View Source
const (
	// Supported signing algorithms
	Algo_RSA_PSS_SHA512    Algorithm = "rsa-pss-sha512"
	Algo_RSA_v1_5_sha256   Algorithm = "rsa-v1_5-sha256"
	Algo_HMAC_SHA256       Algorithm = "hmac-sha256"
	Algo_ECDSA_P256_SHA256 Algorithm = "ecdsa-p256-sha256"
	Algo_ECDSA_P384_SHA384 Algorithm = "ecdsa-p384-sha384"
	Algo_ED25519           Algorithm = "ed25519"

	DigestSHA256 Digest = "sha-256"
	DigestSHA512 Digest = "sha-512"

	// Signature metadata parameters
	MetaCreated   Metadata = "created"
	MetaExpires   Metadata = "expires"
	MetaNonce     Metadata = "nonce"
	MetaAlgorithm Metadata = "alg"
	MetaKeyID     Metadata = "keyid"
	MetaTag       Metadata = "tag"

	// DefaultSignatureLabel is the label that will be used for a signature if not label is provided in the parameters.
	// A request can contain multiple signatures therefore each signature is labeled.
	DefaultSignatureLabel = "sig1"

	// Nonce schemes
	NonceRandom32 = iota // 32 bit random nonce. Base64 encoded

	// Error Codes
	ErrInvalidSignatureOptions ErrCode = "invalid_signature_options"
	ErrInvalidComponent        ErrCode = "invalid_component"
	ErrInvalidSignature        ErrCode = "invalid_signature"
	ErrInvalidAlgorithm        ErrCode = "invalid_algorithm"
	ErrInvalidMetadata         ErrCode = "invalid_metadata"
	ErrInvalidPublicKey        ErrCode = "invalid_public_key"
	ErrInvalidDigest           ErrCode = "invalid_digest"
	ErrInvalidDigestAlgorithm  ErrCode = "invalid_digest_algorithm"
	ErrInvalidHeader           ErrCode = "invalid_header"
	ErrMissingSignature        ErrCode = "missing_signature"
	ErrVerification            ErrCode = "verification" // The signature did not verify according to the algorithm.
	ErrKeyFetch                ErrCode = "key_fetch"    // An error looking up the key for a signature
	ErrUnsupported             ErrCode = "unsupported"  // A particular feature of the spec is not supported
)

Variables

View Source
var (
	DefaultVerifyProfile = VerifyProfile{
		AllowedAlgorithms:  []Algorithm{Algo_ECDSA_P256_SHA256, Algo_ECDSA_P384_SHA384, Algo_ED25519, Algo_HMAC_SHA256},
		RequiredFields:     DefaultRequiredFields,
		RequiredMetadata:   []Metadata{MetaCreated, MetaKeyID},
		DisallowedMetadata: []Metadata{MetaAlgorithm},

		CreatedValidDuration: time.Minute * 5,
		DateFieldSkew:        time.Minute,
	}

	// DefaultRequiredFields covers the request body with 'content-digest' the method and full URI.
	// As per the specification Date is not covered in favor of using the 'created' metadata parameter.
	DefaultRequiredFields = Fields("content-digest", "@method", "@target-uri")
)

Functions

func NewHTTPClient

func NewHTTPClient(hc *http.Client, signer *Signer, verifier *Verifier) *http.Client

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 NewHandler

func NewHandler(h http.Handler, verifier *Verifier) http.Handler

NewHandler wraps an http.Handler with a an http.Handler that verifies each request. verifier cannot be nil.

Example
myhandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	// Lookup the results of verification
	if veriftyResult, ok := httpsig.GetVerifyResult(r.Context()); ok {
		keyid, _ := veriftyResult.KeyID()
		fmt.Fprintf(w, "Hello, %s", keyid)
	} else {
		fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
	}
})

// Create a verifier
verifier, _ := httpsig.NewVerifier(nil, httpsig.DefaultVerifyProfile)

mux := http.NewServeMux()
// Wrap the handler with the a signature verification handler.
mux.Handle("/", httpsig.NewHandler(myhandler, verifier))
Output:

func NewTransport

func NewTransport(rt http.RoundTripper, signer *Signer, verifier *Verifier) http.RoundTripper

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 Sign

func Sign(req *http.Request, params SigningOptions, mdp ...MetadataProvider) error
Example
pkeyEncoded := `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgNTK6255ubaaj1i/c
ppuLouTgjAVyHGSxI0pYX8z1e2GhRANCAASkbVuWv1KXXs2H8b0ruFLyv2lKJWtT
BznPJ5sSI1Jn+srosJB/GbEZ3Kg6PcEi+jODF9fdpNEaHGbbGdaVhJi1
-----END PRIVATE KEY-----`

pkey, _ := keyutil.ReadPrivateKey([]byte(pkeyEncoded))
req := httptest.NewRequest("GET", "https://example.com/data", nil)

params := httpsig.SigningOptions{
	PrivateKey: pkey,
	Algorithm:  httpsig.Algo_ECDSA_P256_SHA256,
	Fields:     httpsig.DefaultRequiredFields,
	Metadata:   []httpsig.Metadata{httpsig.MetaKeyID},
	MetaKeyID:  "key123",
}

signer, _ := httpsig.NewSigner(params)
signer.Sign(req)
Output:

Types

type Algorithm

type Algorithm string

type CreatedScheme

type CreatedScheme int

type Digest

type Digest string

type ErrCode

type ErrCode string

ErrCode enumerates the reasons a signing or verification can fail

type ExpiresScheme

type ExpiresScheme int

type KeyError

type KeyError struct {
	Reason  KeyErrorReason
	Message string
	// contains filtered or unexported fields
}

type KeyErrorReason

type KeyErrorReason string

type KeyFetcher

type KeyFetcher interface {
	// FetchByKeyID looks up a KeySpec from the 'keyid' metadata parameter on the signature.
	FetchByKeyID(keyID string) (KeySpec, error)
	// Fetch looks up a KeySpec when the is not a keyid in the signature.
	Fetch(rh http.Header, md MetadataProvider) (KeySpec, error)
}

type KeySpec

type KeySpec struct {
	KeyID  string
	Algo   Algorithm
	PubKey crypto.PublicKey
	Secret []byte // shared secret for symmetric algorithms
}

type Metadata

type Metadata string

Metadata are the named signature metadata parameters

type MetadataProvider

type MetadataProvider interface {
	Created() (int, error)
	Expires() (int, error)
	Nonce() (string, error)
	Alg() (string, error)
	KeyID() (string, error)
	Tag() (string, error)
}

MetadataProvider allows customized functions for metadata parameter values. Not needed for default usage.

type NonceScheme

type NonceScheme int

type SignatureError

type SignatureError struct {
	Cause   error // may be nil
	Code    ErrCode
	Message string
}

func (*SignatureError) Error

func (se *SignatureError) Error() string

func (*SignatureError) GoString

func (se *SignatureError) GoString() string

func (*SignatureError) Unwrap

func (se *SignatureError) Unwrap() error

type SignedField

type SignedField struct {
	Name       string
	Parameters map[string]any // Parameters are modifiers applied to the field that changes the way the signature is calculated.
}

SignedField indicates which part of the request or response to use for signing. This is the 'message component' in the specification.

func Fields

func Fields(fields ...string) []SignedField

Fields turns a list of fields into the full specification. Used when the signed fields/components do not need to specify any parameters

type Signer

type Signer struct {
	// contains filtered or unexported fields
}

func NewSigner

func NewSigner(params SigningOptions, mdp ...MetadataProvider) (*Signer, error)

func (*Signer) Sign

func (s *Signer) Sign(req *http.Request) error

Sign signs the request and adds the signature headers to the request. If the signature fields includes Content-Digest and Content-Digest is not already included in the request then Sign will read the request body to calculate the digest and set the header. The request body will be replaced with a new io.ReaderCloser.

func (*Signer) SignResponse

func (s *Signer) SignResponse(resp *http.Response) error

type SigningOptions

type SigningOptions struct {
	PrivateKey crypto.PrivateKey // Required for asymetric algorithms
	Secret     []byte            // Required for HMAC signing
	Algorithm  Algorithm
	Digest     Digest        // The http digest algorithm to apply. Defaults to sha-256.
	Fields     []SignedField // Fields and Derived components to sign
	Metadata   []Metadata    // Metadata parameters to add to the signature
	Label      string        // The signature label. Defaults to DefaultSignatureLabel

	// Signature metadata settings.
	// These are only added to the signature if the parameter is listed in the Metadata list.
	MetaKeyID           string        // 'keyid' - No default. A value must be provided if the parameter is in Metadata.
	MetaTag             string        // 'tag' - No default. A value must be provided if the parameter is in Metadata.
	MetaExpiresDuration time.Duration // 'expires' - Current time plus this duration. Default duration 5 minutes.
	MetaNonce           NonceScheme   // 'nonce' - Defaults to NonceRandom32

}

func (SigningOptions) Alg

func (so SigningOptions) Alg() (string, error)

func (SigningOptions) Created

func (so SigningOptions) Created() (int, error)

func (SigningOptions) Expires

func (so SigningOptions) Expires() (int, error)

func (SigningOptions) KeyID

func (so SigningOptions) KeyID() (string, error)

func (SigningOptions) Nonce

func (so SigningOptions) Nonce() (string, error)

func (SigningOptions) Tag

func (so SigningOptions) Tag() (string, error)

type VerifiedSignature

type VerifiedSignature struct {
	Label    string // Label should not be used for the identity of the caller. Use keyid or tag instead. Label can be set by an attacker.
	Metadata MetadataProvider
}

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

func NewVerifier

func NewVerifier(kf KeyFetcher, profile VerifyProfile) (*Verifier, error)

func (*Verifier) Verify

func (ver *Verifier) Verify(req *http.Request) (VerifyResult, error)

func (*Verifier) VerifyResponse

func (ver *Verifier) VerifyResponse(resp *http.Response) (VerifyResult, error)

type VerifyHandler

type VerifyHandler struct {
	// contains filtered or unexported fields
}

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.

func (VerifyHandler) ServeHTTP

func (vh VerifyHandler) ServeHTTP(rw http.ResponseWriter, inReq *http.Request)

type VerifyProfile

type VerifyProfile struct {
	RequiredFields     []SignedField
	RequiredMetadata   []Metadata
	DisallowedMetadata []Metadata
	AllowedAlgorithms  []Algorithm // Which algorithms are allowed, either from keyid meta or in the KeySpec

	// Timing enforcement options
	DisableTimeEnforcement       bool          // If true do no time enforcement on any fields
	DisableExpirationEnforcement bool          // If expiration is present default to enforce
	CreatedValidDuration         time.Duration // Duration allowed for between time.Now and the created time
	ExpiredSkew                  time.Duration // Maximum duration allowed between time.Now and the expired time
	DateFieldSkew                time.Duration // Maximum duration allowed between Date field and created

}

VerifyProfile sets the parameters for a fully valid request or response. A valid signature is a relatively easy accomplishment. Did the signature include of the request? Did it use a strong enough algorithm? Was it signed 41 days ago? There are choices to make about what constitutes a valid signed request or response.

type VerifyResult

type VerifyResult struct {
	Signatures []VerifiedSignature
}

func GetVerifyResult

func GetVerifyResult(ctx context.Context) (v VerifyResult, found bool)

GetVerifyResult returns the results of a successful request signature verification.

func Verify

func Verify(req *http.Request, kf KeyFetcher, profile VerifyProfile) (VerifyResult, error)

Verify validates the signatures in a request and ensured the signature meets the required profile.

Example
pubkeyEncoded := `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIUctKvU5L/eEYxua5Zlz0HIQJRQq
MTQ7eYQXwqpTvTJkuTffGXKLilT75wY2YZWfybv9flu5d6bCfw+4UB9+cg==
-----END PUBLIC KEY-----`

pubkey, _ := keyutil.ReadPublicKey([]byte(pubkeyEncoded))
req := httptest.NewRequest("GET", "https://example.com/data", nil)

kf := keyman.NewKeyFetchInMemory(map[string]httpsig.KeySpec{
	"key123": {
		KeyID:  "key123",
		Algo:   httpsig.Algo_ECDSA_P256_SHA256,
		PubKey: pubkey,
	},
})

httpsig.Verify(req, kf, httpsig.DefaultVerifyProfile)
Output:

func VerifyResponse

func VerifyResponse(resp *http.Response, kf KeyFetcher, profile VerifyProfile) (VerifyResult, error)

func (VerifyResult) KeyID

func (vr VerifyResult) KeyID() (string, error)

KeyID returns the key id of the signature. An error is returned if there is more than one signature.

Directories

Path Synopsis
keyman provides key management functionality
keyman provides key management functionality

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL