signed

package
v0.0.0-...-202847b Latest Latest
Warning

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

Go to latest
Published: Jan 1, 2023 License: Apache-2.0 Imports: 24 Imported by: 1

Documentation

Overview

Package signed provides a middleware to sign responses and validate requests.

The middlewares can add the signature to the HTTP header or to the HTTP trailer or stores them internally. Store the hashes internally allows a transparent validation mechanism.

With the use of HTTPS between two parties this package might not be needed.

If you consider to use a 3rd untrusted participant then this package may help you. For example the 3rd participant is a mobile app. This app requests a product and a dynamic unique calculated price from the backend. The backend sends the product and its price to the mobile app. The app displays the information to the user. The user might add the product with that price to the cart by sending the product and the price back to the backend. The app uses the initial request which the app has received from the backend. The mobile app now simply forwards the unchanged first request bytes back to the backend. The backend can verify the request body by recalculating the hash found in the header.

TODO(CyS) create a flowchart to demonstrate the usage.

https://tools.ietf.org/html/draft-thomson-http-content-signature-00 https://tools.ietf.org/html/draft-burke-content-signature-00

Index

Constants

View Source
const (
	HeaderContentSignature = "Content-Signature"
	HeaderContentHMAC      = "Content-Hmac"
)

Content* constants are used as HTTP header key names.

View Source
const DefaultHashName = `sha256`

DefaultHashName identifies the default hash when creating a new scoped configuration. You must register this name before using this package via:

hashpool.Register(`sha256`, sha256.New)

If you would like to use different hashes you must registered them also in the hashpool package.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cacher

type Cacher interface {
	Set(hash []byte, ttl time.Duration) error
	Has(hash []byte) bool
}

Cacher defines a custom cache type in conjunction with Transparent hashing. It will be used as an underlying storage for the hash values. In middleware WithResponseSignature a hash gets added to the Cacher with a time to live (TTL). The middleware WithRequestSignatureValidation checks if the hashed body matches the cache. The Cacher implementation itself must handle the ttl and the constant time comparison.

type ContentHMAC

type ContentHMAC struct {
	// Algorithm parameter is used if the client and server agree on a
	// non-standard digital signature algorithm.  The full list of supported
	// signature mechanisms is listed below. REQUIRED.
	Algorithm string
	// HeaderKey (optional) a field name in the HTTP header, defaults to
	// Content-HMAC.
	HeaderName string
	// EncodeFn (optional) defines the byte to string encoding function.
	// Defaults to hex.EncodeString.
	EncodeFn
	// DecodeFn (optional) defines the string to byte decoding function.
	// Defaults to hex.DecodeString.
	DecodeFn
}

ContentHMAC writes a simple Content-HMAC header. It can additionally parse a request and return the raw signature.

func NewContentHMAC

func NewContentHMAC(algorithm string) *ContentHMAC

NewContentHMAC creates a new header HMAC object with default hex encoding/decoding to write and parse the Content-HMAC field.

func (*ContentHMAC) HeaderKey

func (h *ContentHMAC) HeaderKey() string

HeaderKey returns the name of the header key

func (*ContentHMAC) Parse

func (h *ContentHMAC) Parse(r *http.Request) (signature []byte, _ error)

Parse looks up the header or trailer for the HeaderKey Content-HMAC in an HTTP request and extracts the raw decoded signature. Errors can have the behaviour: NotFound or NotValid.

func (*ContentHMAC) Write

func (h *ContentHMAC) Write(w http.ResponseWriter, signature []byte)

Writes writes the signature into the response. Content-HMAC: <hash mechanism> <encoded binary HMAC> Content-HMAC: sha1 f1wOnLLwcTexwCSRCNXEAKPDm+U=

type ContentSignature

type ContentSignature struct {
	// KeyID field is an opaque string that the server/client can use to look up
	// the component they need to validate the signature. It could be an SSH key
	// fingerprint, an LDAP DN, etc. REQUIRED.
	KeyID string
	// Separator defines the field separator and defaults to colon.
	Separator rune
	ContentHMAC
}

ContentSignature represents an HTTP Header or Trailer entry with the default header key Content-Signature.

func NewContentSignature

func NewContentSignature(keyID, algorithm string) *ContentSignature

NewContentSignature creates a new header signature object with default hex encoding/decoding to write and parse the Content-Signature field.

func (*ContentSignature) HeaderKey

func (s *ContentSignature) HeaderKey() string

HeaderKey returns the name of the header key

func (*ContentSignature) Parse

func (s *ContentSignature) Parse(r *http.Request) (signature []byte, _ error)

Parse looks up the header or trailer for the HeaderKey Content-Signature in an HTTP request and extracts the raw decoded signature. Errors can have the behaviour: NotFound or NotValid.

func (*ContentSignature) Write

func (s *ContentSignature) Write(w http.ResponseWriter, signature []byte)

Write writes the content signature header using an encoder, which can be hex or base64.

Signature parameter is an encoded digital signature generated by the client. The client uses the `algorithm` and `headers` request parameters to form a canonicalized `signing string`. This `signing string` is then signed with the key associated with `keyId` and the algorithm corresponding to `algorithm`. The `signature` parameter is then set to the encoding of the signature.

Content-Signature: keyId="rsa-key-1",algorithm="rsa-sha256",signature="Hex|Base64(RSA-SHA256(signing string))"
Content-Signature: keyId="hmac-key-1",algorithm="hmac-sha1",signature="Hex|Base64(HMAC-SHA1(signing string))"

type DecodeFn

type DecodeFn func(s string) ([]byte, error)

DecodeFn decodes a raw signature from the header to a byte slice. Useful types are hex.DecodeString or base64.StdEncoding.DecodeString.

type EncodeFn

type EncodeFn func(src []byte) string

EncodeFn encodes a raw signature byte slice to a string. Useful types are hex.EncodeToString or base64.StdEncoding.EncodeToString.

type HeaderParseWriter

type HeaderParseWriter interface {
	HeaderKey() string
	// Write writes a signature to the HTTP response header.
	Write(w http.ResponseWriter, signature []byte)
	// Parse parses from a request the necessary data to find the signature hash
	// and returns the raw signatures byte slice for further validation.
	Parse(r *http.Request) (signature []byte, err error)
}

HeaderParseWriter knows how to read and write the HTTP header in regards to the hash.

type Option

type Option func(*Service) error

Option can be used as an argument in NewService to configure it with different settings.

func OptionsError

func OptionsError(err error) []Option

OptionsError helper function to be used within the backend package or other sub-packages whose functions may return an OptionFactoryFunc.

func WithAllowedMethods

func WithAllowedMethods(methods []string, scopeIDs ...scope.TypeID) Option

WithAllowedMethods sets the allowed HTTP methods which can transport a signature hash.

func WithDebugLog

func WithDebugLog(w io.Writer) Option

WithDebugLog creates a new standard library based logger with debug mode enabled. The passed writer must be thread safe.

func WithDefaultConfig

func WithDefaultConfig(h scope.TypeID) Option

WithDefaultConfig applies the default signed configuration settings based for a specific scope. This function overwrites any previous set options.

Default settings: InTrailer activated, Content-HMAC header with sha256, allowed HTTP methods set to POST, PUT, PATCH and password for the HMAC SHA 256 from a cryptographically random source with a length of 64 bytes. Example:

s := MustNewService(WithDefaultConfig(scope.Store,1), WithOtherSettings(scope.Store, 1, ...))

func WithDisable

func WithDisable(isDisabled bool, scopeIDs ...scope.TypeID) Option

WithDisable disables the current service and calls the next HTTP handler.

The variadic "scopeIDs" argument define to which scope the value gets applied and from which parent scope should be inherited. Setting no "scopeIDs" sets the value to the default scope. Setting one scope.TypeID defines the primary scope to which the value will be applied. Subsequent scope.TypeID are defining the fall back parent scopes to inherit the default or previously applied configuration from.

func WithErrorHandler

func WithErrorHandler(eh mw.ErrorHandler, scopeIDs ...scope.TypeID) Option

WithErrorHandler adds a custom error handler. Gets called in the http.Handler after the scope can be extracted from the context.Context and the configuration has been found and is valid. The default error handler prints the error to the user and returns a http.StatusServiceUnavailable.

The variadic "scopeIDs" argument define to which scope the value gets applied and from which parent scope should be inherited. Setting no "scopeIDs" sets the value to the default scope. Setting one scope.TypeID defines the primary scope to which the value will be applied. Subsequent scope.TypeID are defining the fall back parent scopes to inherit the default or previously applied configuration from.

func WithHash

func WithHash(name string, key []byte, scopeIDs ...scope.TypeID) Option

WithHash sets the hashing algorithm to create a new hash and verify an incoming hash. Please use only cryptographically secure hash algorithms.

func WithHeaderHandler

func WithHeaderHandler(pw HeaderParseWriter, scopeIDs ...scope.TypeID) Option

WithHeaderHandler sets the writer and the parser. The writer knows how to write the hash value into the HTTP header. The parser knows how and where to extract the hash value from the header or even the trailer. Compatible types in this package are ContentHMAC, ContentSignature and Transparent.

func WithLogger

func WithLogger(l log.Logger) Option

WithLogger convenient helper function to apply a logger to the Service type.

func WithMarkPartiallyApplied

func WithMarkPartiallyApplied(partially bool, scopeIDs ...scope.TypeID) Option

WithMarkPartiallyApplied if set to true marks a configuration for a scope as partially applied with functional options set via source code. The internal service knows that it must trigger additionally the OptionFactoryFunc to load configuration from a backend. Useful in the case where parts of the configurations are coming from backend storages and other parts like http handler have been set via code. This function should only be applied in case you work with WithOptionFactory().

The variadic "scopeIDs" argument define to which scope the value gets applied and from which parent scope should be inherited. Setting no "scopeIDs" sets the value to the default scope. Setting one scope.TypeID defines the primary scope to which the value will be applied. Subsequent scope.TypeID are defining the fall back parent scopes to inherit the default or previously applied configuration from.

func WithOptionFactory

func WithOptionFactory(f OptionFactoryFunc) Option

WithOptionFactory applies a function which lazily loads the options from a slow backend (config.Getter) depending on the incoming scope within a request. For example applies the backend configuration to the service.

Once this option function has been set all other manually set option functions, which accept a scope and a scope ID as an argument, will NOT be overwritten by the new values retrieved from the configuration service.

cfgStruct, err := backendsigned.NewConfigStructure()
if err != nil {
	panic(err)
}
be := backendsigned.New(cfgStruct)

srv := signed.MustNewService(
	signed.WithOptionFactory(be.PrepareOptions()),
)

func WithRootConfig

func WithRootConfig(cg config.Getter) Option

WithRootConfig sets the root configuration service to retrieve the scoped base configuration. If you set the option WithOptionFactory() then the option WithRootConfig() does not need to be set as it won't get used.

func WithServiceErrorHandler

func WithServiceErrorHandler(eh mw.ErrorHandler) Option

WithServiceErrorHandler sets the error handler on the Service object. Convenient helper function.

func WithTrailer

func WithTrailer(inTrailer bool, scopeIDs ...scope.TypeID) Option

WithTrailer allows to write the hash sum into the trailer. The middleware switches to stream based hash calculation which results in faster processing instead of writing into a buffer. Make sure that your client can process HTTP trailers.

func WithTransparent

func WithTransparent(c Cacher, ttl time.Duration, scopeIDs ...scope.TypeID) Option

WithTransparent allows to write the hashes into the Cacher with a time-to-live. Responses will not get a header key attached and requests won't get inspected for a header key which might contain the hash value.

type OptionFactories

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

OptionFactories allows to register multiple OptionFactoryFunc identified by their names. Those OptionFactoryFuncs will be loaded in the backend package depending on the configured name under a certain path. This type is embedded in the backendsigned.Configuration type.

func NewOptionFactories

func NewOptionFactories() *OptionFactories

NewOptionFactories creates a new struct and initializes the internal map for the registration of different option factories.

func (*OptionFactories) Deregister

func (of *OptionFactories) Deregister(name string)

Deregister removes a functional option factory from the internal register.

func (*OptionFactories) Lookup

func (of *OptionFactories) Lookup(name string) (OptionFactoryFunc, error)

Lookup returns a functional option factory identified by name or an error if the entry doesn't exists. May return a NotFound error behaviour.

func (*OptionFactories) Names

func (of *OptionFactories) Names() []string

Names returns an unordered list of names of all registered functional option factories.

func (*OptionFactories) Register

func (of *OptionFactories) Register(name string, factory OptionFactoryFunc)

Register adds another functional option factory to the internal register. Overwrites existing entries.

type OptionFactoryFunc

type OptionFactoryFunc func(config.Scoped) []Option

OptionFactoryFunc a closure around a scoped configuration to figure out which options should be returned depending on the scope brought to you during a request.

type ScopedConfig

type ScopedConfig struct {

	// Disabled set to true to disable content signing.
	Disabled bool
	// InTrailer set to true and the signature will be added to the HTTP Trailer for
	// responses.
	InTrailer bool
	// HeaderParseWriter see description of interface HeaderParseWriter
	HeaderParseWriter
	// AllowedMethods list of allowed HTTP methods. Must be upper case.
	AllowedMethods []string
	// TransparentCacher stores the calculated hashes in memory with a TTL. The hash
	// won't get written into the HTTP response. If enable you must set the
	// Cacher field in the Service struct.
	TransparentCacher Cacher
	// TransparentTTL defines the time to live for a hash within the Cacher
	// interface.
	TransparentTTL time.Duration
	// contains filtered or unexported fields
}

ScopedConfig scoped based configuration and should not be embedded into your own types. Call ScopedConfig.ScopeHash to know to which scope this configuration has been bound to.

func (*ScopedConfig) CalculateHash

func (sc *ScopedConfig) CalculateHash(r *http.Request) ([]byte, error)

CalculateHash calculates the hash sum from the request body. The full body gets read into a buffer. This buffer gets assigned to the r.Body to make a read possible for the next consumer.

func (*ScopedConfig) ValidateBody

func (sc *ScopedConfig) ValidateBody(r *http.Request) error

ValidateBody uses the HTTPParser to extract the hash signature. It then hashes the body and compares the hash of the body with the hash value found in the HTTP header. Hash comparison via constant time.

type Service

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

Service creates a middleware that facilitates using a hash function to sign a HTTP body and validate the HTTP body of a request.

func MustNew

func MustNew(opts ...Option) *Service

MustNew same as New() but panics on error. Use only during app start up process.

func New

func New(opts ...Option) (*Service, error)

New creates a new signing middleware for signature creation and validation. The scope.Default and any other scopes have these default settings: InTrailer activated, Content-HMAC header with sha256, allowed HTTP methods set to POST, PUT, PATCH and password for the HMAC SHA 256 from a cryptographically random source with a length of 64 bytes.

func (*Service) ClearCache

func (s *Service) ClearCache() error

ClearCache clears the internal map storing all scoped configurations. You must reapply all functional options. TODO(CyS) all previously applied options will be automatically reapplied.

func (*Service) ConfigByScope

func (s *Service) ConfigByScope(websiteID, storeID int64) (ScopedConfig, error)

ConfigByScope creates a new scoped configuration depending on the Service.useWebsite flag. If useWebsite==true the scoped configuration contains only the website->default scope despite setting a store scope. If an OptionFactory is set the configuration gets loaded from the backend. A nil root config causes a panic.

func (*Service) ConfigByScopeID

func (s *Service) ConfigByScopeID(current scope.TypeID, parent scope.TypeID) (scpCfg ScopedConfig, _ error)

ConfigByScopeID returns the correct configuration for a scope and may fall back to the next higher scope: store -> website -> default. If `current` TypeID is Store, then the `parent` can only be Website or Default. If an entry for a scope cannot be found the next higher scope gets looked up and the pointer of the next higher scope gets assigned to the current scope. This prevents redundant configurations and enables us to change one scope configuration with an impact on all other scopes which depend on the parent scope. A zero `parent` triggers no further look ups. This function does not load any configuration (config.Getter related) from the backend and accesses the internal map of the Service directly.

Important: a "current" scope cannot have multiple "parent" scopes.

func (*Service) ConfigByScopedGetter

func (s *Service) ConfigByScopedGetter(scpGet config.Scoped) (ScopedConfig, error)

ConfigByScopedGetter returns the internal configuration depending on the ScopedGetter. Mainly used within the middleware. If you have applied the option WithOptionFactory() the configuration will be pulled out only one time from the backend configuration service. The field optionInflight handles the guaranteed atomic single loading for each scope.

func (*Service) DebugCache

func (s *Service) DebugCache(w io.Writer) error

DebugCache uses Sprintf to write an ordered list (by scope.TypeID) into a writer. Only usable for debugging.

func (*Service) Options

func (s *Service) Options(opts ...Option) error

Options applies option at creation time or refreshes them.

func (*Service) WithRequestSignatureValidation

func (s *Service) WithRequestSignatureValidation(next http.Handler) http.Handler

WithRequestSignatureValidation extracts from the header or trailer the hash value and hashes the body of the incoming request and compares those two hashes. On success the next handler will be called otherwise the scope based error handler.

func (*Service) WithResponseSignature

func (s *Service) WithResponseSignature(next http.Handler) http.Handler

WithResponseSignature hashes the data written to http.ResponseWriter and adds the hash to the HTTP header. For large data sets use the option InTrailer to provide stream based hashing but the hash gets written into the HTTP trailer. Not all clients can read the HTTP trailer.

type Transparent

type Transparent struct {
	TTL time.Duration
	// contains filtered or unexported fields
}

Transparent stores the calculated hashes in memory with a TTL using the Cacher interface. The hash won't get written into the HTTP response.

func MakeTransparent

func MakeTransparent(c Cacher, ttl time.Duration) Transparent

MakeTransparent creates a new hash writer. Parse is a noop.

func (Transparent) HeaderKey

func (t Transparent) HeaderKey() string

HeaderKey returns an empty string.

func (Transparent) Parse

func (t Transparent) Parse(_ *http.Request) ([]byte, error)

Parse returns always nil,nil.

func (Transparent) Write

func (t Transparent) Write(_ http.ResponseWriter, signature []byte)

Write sets the signature into the cache with the TTL.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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