httpsig

package module
v2.0.4 Latest Latest
Warning

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

Go to latest
Published: Feb 1, 2023 License: BSD-3-Clause Imports: 26 Imported by: 0

README

httpsig

HTTP Signatures made simple

Build Status Go Reference Go Report Card License Chat OpenCollective

go get github.com/go-fed/httpsig

Implementation of HTTP Signatures.

Supports many different combinations of MAC, HMAC signing of hash, or RSA signing of hash schemes. Its goals are:

  • Have a very simple interface for signing and validating
  • Support a variety of signing algorithms and combinations
  • Support setting either headers (Authorization or Signature)
  • Remaining flexible with headers included in the signing string
  • Support both HTTP requests and responses
  • Explicitly not support known-cryptographically weak algorithms
  • Support automatic signing and validating Digest headers

How to use

import "github.com/go-fed/httpsig"

Signing

Signing a request or response requires creating a new Signer and using it:

func sign(privateKey crypto.PrivateKey, pubKeyId string, r *http.Request) error {
	prefs := []httpsig.Algorithm{httpsig.RSA_SHA512, httpsig.RSA_SHA256}
	digestAlgorithm := DigestSha256
	// The "Date" and "Digest" headers must already be set on r, as well as r.URL.
	headersToSign := []string{httpsig.RequestTarget, "date", "digest"}
	signer, chosenAlgo, err := httpsig.NewSigner(prefs, digestAlgorithm, headersToSign, httpsig.Signature)
	if err != nil {
		return err
	}
	// To sign the digest, we need to give the signer a copy of the body...
	// ...but it is optional, no digest will be signed if given "nil"
	body := ...
	// If r were a http.ResponseWriter, call SignResponse instead.
	return signer.SignRequest(privateKey, pubKeyId, r, body)
}

Signers are not safe for concurrent use by goroutines, so be sure to guard access:

type server struct {
	signer httpsig.Signer
	mu *sync.Mutex
}

func (s *server) handlerFunc(w http.ResponseWriter, r *http.Request) {
	privateKey := ...
	pubKeyId := ...
	// Set headers and such on w
	s.mu.Lock()
	defer s.mu.Unlock()
	// To sign the digest, we need to give the signer a copy of the response body...
	// ...but it is optional, no digest will be signed if given "nil"
	body := ...
	err := s.signer.SignResponse(privateKey, pubKeyId, w, body)
	if err != nil {
		...
	}
	...
}

The pubKeyId will be used at verification time.

Verifying

Verifying requires an application to use the pubKeyId to both retrieve the key needed for verification as well as determine the algorithm to use. Use a Verifier:

func verify(r *http.Request) error {
	verifier, err := httpsig.NewVerifier(r)
	if err != nil {
		return err
	}
	pubKeyId := verifier.KeyId()
	var algo httpsig.Algorithm = ...
	var pubKey crypto.PublicKey = ...
	// The verifier will verify the Digest in addition to the HTTP signature
	return verifier.Verify(pubKey, algo)
}

Verifiers are not safe for concurrent use by goroutines, but since they are constructed on a per-request or per-response basis it should not be a common restriction.

Documentation

Overview

Implements HTTP request and response signing and verification. Supports the major MAC and asymmetric key signature algorithms. It has several safety restrictions: One, none of the widely known non-cryptographically safe algorithms are permitted; Two, the RSA SHA256 algorithms must be available in the binary (and it should, barring export restrictions); Finally, the library assumes either the 'Authorizationn' or 'Signature' headers are to be set (but not both).

Index

Constants

View Source
const (

	// RequestTarget specifies to include the http request method and
	// entire URI in the signature. Pass it as a header to NewSigner.
	RequestTarget = "(request-target)"
)

Variables

View Source
var (
	ErrInvalidKey       = errors.New("invalid key")
	ErrInvalidSignature = errors.New("invalid signature")
)

Functions

func ConfigureSigner added in v2.0.4

func ConfigureSigner(
	prefs []Algorithm,
	dAlgo DigestAlgorithm,
	headers []string,
	scheme SignatureScheme,
	expiresIn int64,
) (*Signer, Algorithm, error)

ConfigureSigner creates a new Signer with the provided algorithm preferences to make HTTP signatures. Only the first available algorithm will be used, which is returned by this function along with the Signer. If none of the preferred algorithms were available, then the default algorithm is used. The headers specified will be included into the HTTP signatures.

The Digest will also be calculated on a request's body using the provided digest algorithm, if "Digest" is one of the headers listed.

The provided scheme determines which header is populated with the HTTP Signature.

An error is returned if an unknown or a known cryptographically insecure Algorithm is provided.

func IsSupportedDigestAlgorithm

func IsSupportedDigestAlgorithm(algo string) bool

IsSupportedDigestAlgorithm returns true if hte string is supported by this library, is not a hash known to be weak, and is supported by the hardware.

func IsSupportedHttpSigAlgorithm

func IsSupportedHttpSigAlgorithm(algo string) bool

IsSupportedHttpSigAlgorithm returns true if the string is supported by this library, is not a hash known to be weak, and is supported by the hardware.

func NewSSHSigner

func NewSSHSigner(s ssh.Signer, dAlgo DigestAlgorithm, headers []string, scheme SignatureScheme, expiresIn int64) (SSHSigner, Algorithm, error)

NewwSSHSigner creates a new Signer using the specified ssh.Signer At the moment only ed25519 ssh keys are supported. The headers specified will be included into the HTTP signatures.

The Digest will also be calculated on a request's body using the provided digest algorithm, if "Digest" is one of the headers listed.

The provided scheme determines which header is populated with the HTTP Signature.

Types

type Algorithm

type Algorithm string

Algorithm specifies a cryptography secure algorithm for signing HTTP requests and responses.

const (
	// MAC-based algoirthms.
	HMAC_SHA224      Algorithm = hmacPrefix + "-" + sha224String
	HMAC_SHA256      Algorithm = hmacPrefix + "-" + sha256String
	HMAC_SHA384      Algorithm = hmacPrefix + "-" + sha384String
	HMAC_SHA512      Algorithm = hmacPrefix + "-" + sha512String
	HMAC_RIPEMD160   Algorithm = hmacPrefix + "-" + ripemd160String
	HMAC_SHA3_224    Algorithm = hmacPrefix + "-" + sha3_224String
	HMAC_SHA3_256    Algorithm = hmacPrefix + "-" + sha3_256String
	HMAC_SHA3_384    Algorithm = hmacPrefix + "-" + sha3_384String
	HMAC_SHA3_512    Algorithm = hmacPrefix + "-" + sha3_512String
	HMAC_SHA512_224  Algorithm = hmacPrefix + "-" + sha512_224String
	HMAC_SHA512_256  Algorithm = hmacPrefix + "-" + sha512_256String
	HMAC_BLAKE2S_256 Algorithm = hmacPrefix + "-" + blake2s_256String
	HMAC_BLAKE2B_256 Algorithm = hmacPrefix + "-" + blake2b_256String
	HMAC_BLAKE2B_384 Algorithm = hmacPrefix + "-" + blake2b_384String
	HMAC_BLAKE2B_512 Algorithm = hmacPrefix + "-" + blake2b_512String
	BLAKE2S_256      Algorithm = blake2s_256String
	BLAKE2B_256      Algorithm = blake2b_256String
	BLAKE2B_384      Algorithm = blake2b_384String
	BLAKE2B_512      Algorithm = blake2b_512String
	// RSA-based algorithms.
	RSA_SHA1   Algorithm = rsaPrefix + "-" + sha1String
	RSA_SHA224 Algorithm = rsaPrefix + "-" + sha224String
	// RSA_SHA256 is the default algorithm.
	RSA_SHA256    Algorithm = rsaPrefix + "-" + sha256String
	RSA_SHA384    Algorithm = rsaPrefix + "-" + sha384String
	RSA_SHA512    Algorithm = rsaPrefix + "-" + sha512String
	RSA_RIPEMD160 Algorithm = rsaPrefix + "-" + ripemd160String
	// ECDSA algorithms
	ECDSA_SHA224    Algorithm = ecdsaPrefix + "-" + sha224String
	ECDSA_SHA256    Algorithm = ecdsaPrefix + "-" + sha256String
	ECDSA_SHA384    Algorithm = ecdsaPrefix + "-" + sha384String
	ECDSA_SHA512    Algorithm = ecdsaPrefix + "-" + sha512String
	ECDSA_RIPEMD160 Algorithm = ecdsaPrefix + "-" + ripemd160String
	// ED25519 algorithms
	// can only be SHA512
	ED25519 Algorithm = ed25519Prefix
)

type DigestAlgorithm

type DigestAlgorithm string
const (
	DigestSha256 DigestAlgorithm = "SHA-256"
	DigestSha512 DigestAlgorithm = "SHA-512"
)

type ECDSASignature

type ECDSASignature struct {
	R, S *big.Int
}

type SSHSigner

type SSHSigner interface {
	// SignRequest signs the request using ssh.Signer.
	// The public key id is used by the HTTP server to identify which key to use
	// to verify the signature.
	//
	// A Digest (RFC 3230) will be added to the request. The body provided
	// must match the body used in the request, and is allowed to be nil.
	// The Digest ensures the request body is not tampered with in flight,
	// and if the signer is created to also sign the "Digest" header, the
	// HTTP Signature will then ensure both the Digest and body are not both
	// modified to maliciously represent different content.
	SignRequest(pubKeyId string, r *http.Request, body []byte) error
	// SignResponse signs the response using ssh.Signer. The public key
	// id is used by the HTTP client to identify which key to use to verify
	// the signature.
	//
	// A Digest (RFC 3230) will be added to the response. The body provided
	// must match the body written in the response, and is allowed to be
	// nil. The Digest ensures the response body is not tampered with in
	// flight, and if the signer is created to also sign the "Digest"
	// header, the HTTP Signature will then ensure both the Digest and body
	// are not both modified to maliciously represent different content.
	SignResponse(pubKeyId string, r http.ResponseWriter, body []byte) error
}

Signers will sign HTTP requests or responses based on the algorithms and headers selected at creation time.

Signers are not safe to use between multiple goroutines.

Note that signatures do set the deprecated 'algorithm' parameter for backwards compatibility.

type SignatureScheme

type SignatureScheme string

HTTP Signatures can be applied to different HTTP headers, depending on the expected application behavior.

const (
	// Signature will place the HTTP Signature into the 'Signature' HTTP
	// header.
	Signature SignatureScheme = "Signature"
	// Authorization will place the HTTP Signature into the 'Authorization'
	// HTTP header.
	Authorization SignatureScheme = "Authorization"
)

type Signer

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

Signers will sign HTTP requests or responses based on the algorithms and headers selected at creation time.

Signers are not safe to use between multiple goroutines.

Note that signatures do set the deprecated 'algorithm' parameter for backwards compatibility.

func NewSigner

func NewSigner(
	m SigningMethod,
	dAlgo DigestAlgorithm,
	headers []string,
	scheme SignatureScheme,
	expiresIn int64,
) *Signer

func (*Signer) SignRequest

func (s *Signer) SignRequest(
	pKey crypto.PrivateKey,
	pubKeyId string,
	r *http.Request,
	body []byte,
) error

SignRequest signs the request using a private key. The public key id is used by the HTTP server to identify which key to use to verify the signature.

If the Signer was created using a MAC based algorithm, then the key is expected to be of type []byte. If the Signer was created using an RSA based algorithm, then the private key is expected to be of type *rsa.PrivateKey.

A Digest (RFC 3230) will be added to the request. The body provided must match the body used in the request, and is allowed to be nil. The Digest ensures the request body is not tampered with in flight, and if the signer is created to also sign the "Digest" header, the HTTP Signature will then ensure both the Digest and body are not both modified to maliciously represent different content.

func (*Signer) SignResponse

func (s *Signer) SignResponse(pKey crypto.PrivateKey, pubKeyId string, r http.ResponseWriter, body []byte) error

SignResponse signs the response using a private key. The public key id is used by the HTTP client to identify which key to use to verify the signature.

If the Signer was created using a MAC based algorithm, then the key is expected to be of type []byte. If the Signer was created using an RSA based algorithm, then the private key is expected to be of type *rsa.PrivateKey.

A Digest (RFC 3230) will be added to the response. The body provided must match the body written in the response, and is allowed to be nil. The Digest ensures the response body is not tampered with in flight, and if the signer is created to also sign the "Digest" header, the HTTP Signature will then ensure both the Digest and body are not both modified to maliciously represent different content.

type SigningMethod

type SigningMethod interface {
	Sign(key interface{}, data []byte) ([]byte, error)
	Verify(key interface{}, data, signature []byte) error
	String() string
}

type Verifier

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

Verifier verifies HTTP Signatures.

It will determine which of the supported headers has the parameters that define the signature.

Verifiers are not safe to use between multiple goroutines.

Note that verification ignores the deprecated 'algorithm' parameter.

func NewResponseVerifier

func NewResponseVerifier(r *http.Response) (*Verifier, error)

NewResponseVerifier verifies the given response. It returns errors under the same conditions as NewVerifier.

func NewVerifier

func NewVerifier(r *http.Request) (*Verifier, error)

NewVerifier verifies the given request. It returns an error if the HTTP Signature parameters are not present in any headers, are present in more than one header, are malformed, or are missing required parameters. It ignores unknown HTTP Signature parameters.

func (*Verifier) KeyId

func (v *Verifier) KeyId() string

KeyId gets the public key id that the signature is signed with.

Note that the application is expected to determine the algorithm used based on metadata or out-of-band information for this key id.

func (*Verifier) Verify

func (v *Verifier) Verify(key interface{}, algo Algorithm) error

Verify accepts the public key specified by KeyId and returns an error if verification fails or if the signature is malformed. The algorithm must be the one used to create the signature in order to pass verification. The algorithm is determined based on metadata or out-of-band information for the key id.

If the signature was created using a MAC based algorithm, then the key is expected to be of type []byte. If the signature was created using an RSA based algorithm, then the public key is expected to be of type *rsa.PublicKey.

Jump to

Keyboard shortcuts

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