sigs

package
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Feb 29, 2024 License: Apache-2.0 Imports: 23 Imported by: 2

README

Hannibal / sigs

Common-Sense HTTP Signatures

Go Reference Build Status Go Report Card Codecov

This library is a simple-yet-thorough implementation the IETF HTTP Signatures specification. It aims to be extensively tested and documented, with extensions for you to test and troubleshoot your own implementations.

Project Status (DO NOT USE)

This code is still being developed and is not ready to use.

Key Features

  • Simple API with sensible defaults
  • Complete implementation of all commonly used cipher and digest algorithms
  • Extensive use of zerolog logging to simplify troubleshooting

Signing Outbound Requests

The sigs library makes it easy to sign an outbound http.Request. It includes sensible defaults (shown below) so that most uses should "just work" with minimal configuration.

// Generate a private key to sign. Real code would likely
// retrieve this from a database.
privateKey, err := rsa.GeneratePrivateKey(rand.Reader, 2048)

// Generate an http.Request to sign. Real code would likely
// set additional values in the outbound request.
request := http.NewRequest("POST", "https://example.com", nil)

// Sign the Request with the Private Key. Yes, that's it.
if err := sigs.Sign(request, privateKey); err != nil {
	// handle error...
}

Signing Options

In the event that you need to customize the way you sign a Request, you can pass one or more optional functions into the Sign function.

Option Description Default
SignerFields Sets the field(s) to use when creating the "Signature" header . (request-target) host date digest
SignerSignatureHash Sets the algorithm to use when validating the "Signature" header. sha-256
SignerBodyDigests Sets the algorithm to use when creating the "Digest" header. sha-256
// How to sign a request using additional options
err := sigs.Sign(
	request, 
	privateKey,
	sigs.SignatureFields("(request-target)", "(created)", "(expires)"),
	sigs.SignatureDigest("sha-512"),
)
Object Notation

In most cases, the above syntax is the simplest way to use sigs. However, the library also publishes the underlying objects used to sign http.Requests, which you can also access directly. For instance, you may need to do this if you need to use complex logic to determine what options to set.

signer := sigs.NewSigner()
signer.Use(SignFields("content-type", "date"))

if err := signer.Sign(request, privateKey); err != nil {
	// handle error...
}

Verifying Inbound Requests

// Define an http.Request.  Real code would receive this request
// from a remote server in an http.Handler function
request := http.NewRequest("POST", https://example.com", nil)

// Define the PEM-encoded certificate.  Real code would
// retrieve this from the remote user's profile.
publicKeyPEM := `-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----`

// Verify the request has a valid signature from the certificate.
// Yes, that's it.
if err := sigs.Verify(request, publicKeyPEM); err != nil {
	// handle error...
}
Verification Options

In the event that you need to customize the way you verify a Request, you can pass one or more optional functions into the Verify function.

Option Description Default
VerifierFields Sets the list of fields that MUST ALL be present in the signature. Additional fields are allowed in the signature, and will still be verified. (request-target) host date digest
VerifierBodyDigests Sets the list of algorithms to acccept from remote servers when they create a "Digest" header. ALL recofnized digests must be valid to pass, and AT LEAST ONE of the algorithms must be from this list. If present, additional algorithms will be ignored. sha-256
VerifierSignatureHashes Sets the hashing algorithms to try when validating the "Signature" header. Validation fails if checks on ALL algorithms are unsuccessful. sha-256, sha-512
// How to verify a request using additional options
err := sigs.Verify(
	request, 
	publicKeyPEM,
	sigs.VerifyFields("(request-target)", "(created)", "(expires)"),
	sigs.VerifyDigest("sha-512"),
)
Object Notation

In most cases, the above syntax is the simplest way to use sigs. However, the library also publishes the underlying objects used to verfy http.Requests, which you can also access directly. For instance, you may need to do this if you need to use complex logic to determine what options to set.

verifier := sigs.NewVerifier()
verifier.Use(VerifyFields("content-type","date"))

if err := verifier.Verify(request, publicKeyPEM); err != nil {
	// handle error...
}

Troubleshooting

The sigs library generates fine grained debugging information with zerolog structured logging library. By default, it sets the logging level to Disabled so that no logging information is written. If you need to see deeper into sigs add the following into your application code:

func main() {

	// zerolog.SetGlobalLevel(zerolog.Trace) // for a step-by-step trace of every sigs action.
	// zerolog.SetGlobalLevel(zerolog.Debug) // for higher-level debugging of signatures and verification
	
	// your code here...
}

Supported Algorithms

Signatures
  • hs2019
  • rsa-sha256
  • rsa-sha512
  • hmac-sha256 (In Progress)
  • ecdsa-sha256 (In Progress)
Digests
  • sha256
  • sha512

References

Documentation

Overview

Package sigs implements the IETF draft specification "Signing HTTP Messages" https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures

Index

Constants

View Source
const Algorithm_ECDSA_SHA256 = "ecdsa-sha256"

Deprecated. The “ecdsa-sha256” signature algorithm. Deprecated by the standard because it reveals which hash and digest algorithm is used.

View Source
const Algorithm_ECDSA_SHA512 = "ecdsa-sha512"
View Source
const Algorithm_HMAC_SHA256 = "hmac-sha256"

Deprecated. The “hmac-sha256” signature algorithm. Deprecated by the standard because it reveals which hash and digest algorithm is used.

View Source
const Algorithm_HMAC_SHA512 = "hmac-sha512"

TODO: Are these supported by the actual specs?

View Source
const Algorithm_HS2019 = "hs2019"

The “hs2019” signature algorithm. This is the only non-deprecated algorithm. Unlike the other algorithms, the hash and digest functions are not implied by the choice of this signature algorithm. Instead, the hash and digest functions are chosen based on the key used. RSA, HMAC, and ECDSA keys are all supported. TODO: How to implement hs2019?

View Source
const Algorithm_RSA_SHA256 = "rsa-sha256"

Deprecated. The “rsa-sha256” signature algorithm. Deprecated by the standard because it reveals which hash and digest algorithm is used.

View Source
const Algorithm_RSA_SHA512 = "rsa-sha512"
View Source
const FieldCreated = "(created)"

FieldCreated is not supported at this time, and will generate an error. https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures#section-2.3

View Source
const FieldDate = "date"
View Source
const FieldDigest = "digest"

FieldDigest represents the Digest header field that validates the request body. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Digest https://datatracker.ietf.org/doc/draft-ietf-httpbis-digest-headers/

View Source
const FieldExpires = "(expires)"

FieldExpires is not supported at this time, and will generate an error. https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures#section-2.3

View Source
const FieldHost = "host"
View Source
const FieldRequestTarget = "(request-target)"

Variables

This section is empty.

Functions

func ApplyDigest

func ApplyDigest(request *http.Request, digestName string, digestFunc DigestFunc) error

ApplyDigest calculates the digest of the body from a given http.Request, then adds the digest to the Request's header.

func DecodePrivatePEM

func DecodePrivatePEM(pemString string) (crypto.PrivateKey, error)

DecodePrivatePEM converts a PEM string into a private key

func DecodePublicPEM

func DecodePublicPEM(pemString string) (crypto.PublicKey, error)

DecodePublicPEM converts a PEM string into a public key

func DigestSHA256

func DigestSHA256(body []byte) string

DigestSHA256 calculates the SHA-256 digest of a slice of bytes

func DigestSHA512

func DigestSHA512(body []byte) string

DigestSHA512 calculates the SHA-512 digest of a given slice of bytes

func EncodePrivatePEM

func EncodePrivatePEM(privateKey *rsa.PrivateKey) string

EncodePrivatePEM converts a private key into a PEM string

func EncodePublicPEM

func EncodePublicPEM(privateKey *rsa.PrivateKey) string

EncodePublicPEM converts a public key into a PEM string

func Sign

func Sign(request *http.Request, publicKeyID string, privateKey crypto.PrivateKey, options ...SignerOption) error

Sign signs a given http.Request. It is syntactic sugar for NewSigner(options...).Sign(request)

func Verify

func Verify(request *http.Request, keyFinder PublicKeyFinder, options ...VerifierOption) error

Verify verifies the given http.Request. This is syntactic sugar for NewVerifier(options...).Verify(request)

func VerifyDigest

func VerifyDigest(request *http.Request, allowedHashes ...crypto.Hash) error

VerifyDigest verifies that the digest in the http.Request header matches the contents of the http.Request body.

func WithSigner added in v0.8.1

func WithSigner(signer Signer) remote.Option

WithSigner is a remote.Option that signs an outbound HTTP request

Types

type DigestFunc

type DigestFunc func(body []byte) string

DigestFunc defines a function that calculates the digest of a given byte array

type PublicKeyFinder added in v0.10.0

type PublicKeyFinder func(keyID string) (string, error)

SignatureFinder is a function that can look up a public key. This is injected into the Verify function by the inbox.

type Signature

type Signature struct {
	KeyID     string   // ID (URL) of the key used to create this signature
	Algorithm string   // Algorithm used to create this signature (should be ignored per IEFT spec)
	Headers   []string // List of headers that were signed
	Signature []byte   // Base64 encoded signature
	Created   int64    // Unix epoch (in seconds) when this signature was created
	Expires   int64    // Unix epoch (in seconds) when this signature expires
}

https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures#section-2.1

func NewSignature

func NewSignature() Signature

NewSignature returns a fully initialized Signature object

func ParseSignature

func ParseSignature(value string) (Signature, error)

ParseSignature parses a string into an HTTP Signature

func (Signature) AlgorithmPrefix

func (signature Signature) AlgorithmPrefix() string

AlgorithmPrefix returns the first part of the algorithm name, such as "rsa", "hmac", or "ecdsa"

func (Signature) Base64

func (signature Signature) Base64() string

SignatureBytes returns the signature as a slice of bytes

func (Signature) Bytes

func (signature Signature) Bytes() []byte

Bytes returns the Signature as a slice of bytes

func (Signature) CreatedString

func (signature Signature) CreatedString() string

func (Signature) ExpiresString

func (signature Signature) ExpiresString() string

func (Signature) IsExpired

func (signature Signature) IsExpired(duration int) bool

IsExpired returns TRUE if the current date is less than its expiration date, OR if the createDate + duration is less than the current date. Calculations are skipped if the duration, created, or expires values are zero.

func (Signature) String

func (signature Signature) String() string

String returns the Signature as a string

type Signer

type Signer struct {
	PublicKeyID   string
	PrivateKey    crypto.PrivateKey
	Fields        []string
	SignatureHash crypto.Hash
	BodyDigest    crypto.Hash
	HS2019        bool
	Created       int64
	Expires       int64
}

Signer contains all of the settings necessary to sign a request

func NewSigner

func NewSigner(publicKeyID string, privateKey crypto.PrivateKey, options ...SignerOption) Signer

NewSigner returns a fully initialized Signer

func (*Signer) MakeSignature

func (signer *Signer) MakeSignature(request *http.Request) (Signature, error)

MakeSignature generates a Signature string for the given http.Request

func (*Signer) Sign added in v0.8.1

func (signer *Signer) Sign(request *http.Request) error

Sign generates a signature and applies it to the given http.Request

func (*Signer) With added in v0.8.1

func (signer *Signer) With(options ...SignerOption)

Use applies the given options to the Signer

type SignerOption

type SignerOption func(*Signer)

SignerOption is a function that modifies a Signer

func SignerBodyDigest

func SignerBodyDigest(digest crypto.Hash) SignerOption

SignerBodyDigests sets the digest algorithm to be used when creating the "Digest" header.

func SignerCreated

func SignerCreated(created int64) SignerOption

func SignerExpires

func SignerExpires(expires int64) SignerOption

func SignerFields

func SignerFields(fields ...string) SignerOption

SignerFields sets the http.Request fields to be signed

func SignerSignatureHash

func SignerSignatureHash(hash crypto.Hash) SignerOption

SignerSignatureDigest sets the hashing algorithm to be used when we sign a request.

type Verifier

type Verifier struct {
	Fields          []string
	BodyDigests     []crypto.Hash // List of algorithms to accept from remote servers when they create a Digest header.  Default is SHA256 and SHA512
	SignatureHashes []crypto.Hash // Digest algorithm used to create the signature.  Default is SHA256, SHA512
	Timeout         int           // Number of seconds before signatures are expired. Default is 43200 seconds (12 hours).
	CheckDigest     bool          // If true, then the verifier will check the Digest header.  Default is true.
}

Verifier contains all of the settings necessary to verify a request

func NewVerifier

func NewVerifier(options ...VerifierOption) Verifier

NewVerifier returns a fully initialized Verifier

func (*Verifier) Use

func (verifier *Verifier) Use(options ...VerifierOption)

Use applies the given options to the Verifier

func (*Verifier) Verify

func (verifier *Verifier) Verify(request *http.Request, keyFinder PublicKeyFinder) error

Verify verifies the given http.Request

type VerifierOption

type VerifierOption func(*Verifier)

VerifierOption is a function that modifies a Verifier

func VerifierBodyDigests

func VerifierBodyDigests(digests ...crypto.Hash) VerifierOption

VerifierDigests sets the list of algorithms that we will accept from remote servers when they create a "Digest" http header. ALL recognized digests must be valid to pass, and AT LEAST ONE of the algorithms must be from this list.

func VerifierFields

func VerifierFields(fields ...string) VerifierOption

VerifierFields sets the list of http.Request fields that MUST ALL be present in the "Signature" header from a remote server for a signature to be accepted. Extra fields are allowed in the Signature, and will still be verified.

func VerifierIgnoreBodyDigest added in v0.7.0

func VerifierIgnoreBodyDigest() VerifierOption

VerifierIgnoreBodyDigest sets the verifier to ignore the "Digest" header. This is useful for testing but should not be used in production.

func VerifierIgnoreTimeout

func VerifierIgnoreTimeout() VerifierOption

VerifierIgnoreTimeout sets the verifier to ignore message and signature time stamps. This is useful for testing signatures, but should not be used in production.

func VerifierSignatureHashes

func VerifierSignatureHashes(hashes ...crypto.Hash) VerifierOption

VerifierSignatureHashes sets the hashing algorithms to use when validating the "Signature" header. Hashes are tried in order, and the FIRST successful match returns success. If ALL hash attempts fail, then validation fails.

func VerifierTimeout

func VerifierTimeout(seconds int) VerifierOption

VerifierTimeout sets the maximum age of a request and signature (in seconds). Messages received after this time duration will be rejected. Default is 43200 seconds (12 hours).

Jump to

Keyboard shortcuts

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