msgsig

package module
v0.0.0-...-3f97823 Latest Latest
Warning

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

Go to latest
Published: Mar 10, 2022 License: Apache-2.0 Imports: 23 Imported by: 0

README

msgsig: HTTP Message Signatures for Go

This package implements v09 of the HTTP Message Signatures draft specification. It will be updated for future revisions of the spec.

Usage

At a high level, this package provides Signer and Verifier interfaces. Signers and verifiers are not safe for use by multiple Goroutines. Keep a sync.Pool of them (recommended), or protect them with a lock.

Signing messages consists of constructing a Signer and re-using it to sign multiple messages:

	alg, _ := NewAsymmetricSigningAlgorithm(AlgorithmEcdsaP256Sha256, testKeyEccP256Private, testKeyEccP256Name)
	signer, err := NewSigner(alg, WithCoveredComponents("content-type", "digest", "content-length"))

	_ = signer.SignResponse(context.Background(), resp)

Verifying messages is similar in that you construct a verifier and re-use it to verify multiple messages:

	vAlg, _ := NewAsymmetricVerifyingAlgorithm(AlgorithmEcdsaP256Sha256, testKeyEccP256Public, testKeyEccP256Name)

	keyFinder := func(ctx context.Context, keyId string) (VerifyingAlgorithm, bool) {
		if keyId == testKeyEccP256Name {
			return vAlg, true
		}
		return nil, false
	}

	verifier, _ := NewVerifier(keyFinder, WithCoveredComponents("content-type", "digest", "content-length"))

	_ = verifier.VerifyResponse(context.Background(), resp, body)

Unlike signers, it is expected that Verifiers may see signatures for a range of keys, so instead of being parameterized with a single key they take a function to lookup keys by id.

Verify functions take an explicit HTTP body because, in general, Go's http.Response.Body field doesn't buffer the body for multiple readers. It seems least-surprising to have to have the caller read (and buffer) the whole HTTP body that having that happen silently within a call to this library.

For the verifier, the WithCoveredComponents argument is the minimum set of HTTP headers that must be signed. If the request/response's signature doesn't cover at least those fields, verification will fail.

Performance

There is more to optimize, but performance is good (10s of signs and verifies per millisecond) and largely gated by the cost of ECDSA operations:

goos: darwin
goarch: arm64
pkg: github.com/bpowers/msgsig
BenchmarkHmacSha256Sign                 	 7315312	     814.0 ns/op	     256 B/op	       7 allocs/op
BenchmarkEcdsaP256Sha256Sign            	  301438	     19701 ns/op	    2808 B/op	      38 allocs/op
BenchmarkEcdsaP256Sha256Verify          	  105506	     56467 ns/op	    1464 B/op	      27 allocs/op
BenchmarkEcdsaP256Sha256VerifyLargeBody 	     100	  55139152 ns/op	    1818 B/op	      27 allocs/op
BenchmarkEd25519Verify                  	  115123	     51935 ns/op	     312 B/op	       6 allocs/op

The large body tests verification of messages with a 128 MB POST body, where all the additional time is spent generating the SHA256 digest of the body (to compare against the Digest header). On an M1 mac that test takes ~60 milliseconds, but we've observed it take 400 ms on an EC2 m5d instance.

Documentation

Index

Constants

View Source
const (
	SignatureInputHeaderName = "Signature-Input"
	SignatureHeaderName      = "Signature"
	DigestHeaderName         = "Digest"
)

Variables

View Source
var (
	ErrorUnknownAlgorithm           = errors.New("algorithm name not in HTTP Signature Algorithms Registry")
	ErrorAlgorithmKeyMismatch       = errors.New("wrong private key type for specified algorithm")
	ErrorEmptyKeyId                 = errors.New("expected a non-empty key ID")
	ErrorUnknownKeyId               = errors.New("key ID provided, but key lookup failed")
	ErrorInvalidSigLength           = errors.New("the base64-decoded signature has an unexpected length")
	ErrorUnsupportedDigestAlgorithm = errors.New("a digest header was found, but it didn't contain a digest in a supported algorithm")
	ErrorMissingSig                 = errors.New("missing 'Signature' header")
	ErrorMalformedSigInput          = errors.New("malformed 'Signature-Input' header")
	ErrorMalformedSig               = errors.New("malformed 'Signature' header")
	ErrorMissingSigParamsValue      = errors.New("missing expected params for sigid in 'Signature-Input' header")
	ErrorVerifyFailed               = errors.New("failed to verify signature")
	ErrorDigestMismatch             = errors.New("failed to verify content hash in 'Digest' header")
)

Functions

func WithAlg

func WithAlg(alg bool) func(s *sigOptions)

func WithCoveredComponents

func WithCoveredComponents(components ...string) func(s *sigOptions)

func WithCreated

func WithCreated(b bool) func(s *sigOptions)

WithCreated ensures that signatures created by a Signer with this option set have a created signature parameter.

func WithMaxAge

func WithMaxAge(duration time.Duration) func(s *sigOptions)

func WithNoCoveredComponents

func WithNoCoveredComponents() func(s *sigOptions)

func WithNonce

func WithNonce(nonce bool) func(s *sigOptions)

func WithSigNamer

func WithSigNamer(namer func() string) func(s *sigOptions)

Types

type AlgorithmName

type AlgorithmName string
const (
	AlgorithmRsaPssSha512    AlgorithmName = "rsa-pss-sha512"
	AlgorithmRsaV15Sha256    AlgorithmName = "rsa-v1_5-sha256"
	AlgorithmEcdsaP256Sha256 AlgorithmName = "ecdsa-p256-sha256"
	AlgorithmHmacSha256      AlgorithmName = "hmac-sha256"
	AlgorithmEd25519         AlgorithmName = "ed25519"
)

type Signer

type Signer interface {
	Sign(req *http.Request) error
	SignResponse(ctx context.Context, resp *http.Response) error
}

Signer objects sign HTTP requests.

func NewSigner

func NewSigner(alg SigningAlgorithm, opts ...SignerOption) (Signer, error)

NewSigner returns a Signer that can be used to create and attach HTTP message signatures to http.Request and http.Response structs.

type SignerOption

type SignerOption func(options *sigOptions)

type SigningAlgorithm

type SigningAlgorithm interface {
	KeyId() string
	AlgName() AlgorithmName
	Sign(input []byte) ([]byte, error)
}

func NewAsymmetricSigningAlgorithm

func NewAsymmetricSigningAlgorithm(algName AlgorithmName, privKey crypto.Signer, keyId string) (SigningAlgorithm, error)

func NewHmacSha256SigningAlgorithm

func NewHmacSha256SigningAlgorithm(key []byte, keyId string) (SigningAlgorithm, error)

type Verifier

type Verifier interface {
	Verify(req *http.Request, body []byte) error
	VerifyResponse(ctx context.Context, resp *http.Response, body []byte) error
}

func NewVerifier

func NewVerifier(algFinder func(ctx context.Context, keyName string, headers http.Header) (VerifyingAlgorithm, bool), opts ...SignerOption) (Verifier, error)

NewVerifier creates a Verifier instance to verify HTTP Message Signatures and is safe for concurrent use from multiple goroutines. It is intended that you create a single Verifier instance and reuse it across messages. The algFinder parameter is called by the Verifier to find a public key to verify the request with - the header parameter to algFinder MUST NOT be modified.

type VerifyingAlgorithm

type VerifyingAlgorithm interface {
	KeyId() string
	AlgName() AlgorithmName
	Verify(input, sig []byte) (bool, error)
}

func NewAsymmetricVerifyingAlgorithm

func NewAsymmetricVerifyingAlgorithm(algName AlgorithmName, pubKey crypto.PublicKey, keyId string) (VerifyingAlgorithm, error)

Directories

Path Synopsis
Package component contains constants defined in section 2.3: Specialty Components.
Package component contains constants defined in section 2.3: Specialty Components.
internal

Jump to

Keyboard shortcuts

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