Documentation ¶
Overview ¶
Provides tools for signing and authenticating HTTP requests between web services
An keyed-hash message authentication code (HMAC) is used to provide integrity and authenticity of a message between web services. The following elements are input into the HMAC. Only the items in bold are required to be passed in by the user, the other elements are either optional or build by httpsign for you.
Each request element is delimited with the character `|` and each request element is preceded by its length. A simple example with only the required parameters:
// Randomly generated number from a CSPRNG. shared_secret = '042DAD12E0BE4625AC0B2C3F7172DBA8' // Epoch time (number of seconds since January 1, 1970 UTC). timestamp = '1330837567' // Randomly generated number from a CSPRNG. nonce = '000102030405060708090a0b0c0d0e0f' // Request body request_body = '{"hello": "world"}' // Optionally the HTTP Verb and HTTP Request URI. // Optionally an additional headers to sign. signature = HMAC('042DAD12E0BE4625AC0B2C3F7172DBA8', '10|1330837567|32|000102030405060708090a0b0c0d0e0f|18|{"hello": "world"}')
The timestamp, nonce, signature, and signature version are set as headers for the HTTP request to be signed. They are then verified on the receiving side by running the same algorithm and verifying that the signatures match.
Note: By default the service can securely handle authenticating 5,000 requests per second. If you need to authenticate more, increase the capacity of the nonce cache when initializing the package.
Index ¶
- Constants
- type Config
- type NonceCache
- type Service
- func (s *Service) AuthenticateRequest(r *http.Request) error
- func (s *Service) AuthenticateRequestWithKey(r *http.Request, secretKey []byte) (err error)
- func (s *Service) CheckTimestamp(timestampHeader string) (bool, error)
- func (s *Service) SignRequest(r *http.Request) error
- func (s *Service) SignRequestWithKey(r *http.Request, secretKey []byte) error
Examples ¶
Constants ¶
const CacheCapacity = 5000 * CacheTimeout
5,000 msg/sec * 100 sec = 500,000 elements
const CacheTimeout = 100
100 sec
const MaxSkewSec = 5
5 sec
const XMailgunNonce = "X-Mailgun-Nonce"
const XMailgunSignature = "X-Mailgun-Signature"
const XMailgunSignatureVersion = "X-Mailgun-Signature-Version"
const XMailgunTimestamp = "X-Mailgun-Timestamp"
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct { // KeyPath is a path to a file that contains the key to sign requests. If // it is an empty string then the key should be provided in `KeyBytes`. KeyPath string // KeyBytes is a key that is used by lemma to sign requests. Ignored if // `KeyPath` is not an empty string. KeyBytes []byte // List of headers to sign HeadersToSign []string // Include the http verb and uri in request SignVerbAndURI bool // Capacity of the nonce cache NonceCacheCapacity int // Nonce cache timeout NonceCacheTimeout int // Clock to use when signing Clock holster.Clock // Random Number Generator to use when signing Random random.RandomProvider // Toggle emitting metrics or not EmitStats bool // Hostname of statsd server StatsdHost string // Port of statsd server StatsdPort int // Prefix to prepend to metrics StatsdPrefix string // Default: X-Mailgun-Nonce NonceHeaderName string // Default: X-Mailgun-Timestamp TimestampHeaderName string // Default: X-Mailgun-Signature SignatureHeaderName string // Default: X-Mailgun-Signature-Version SignatureVersionHeaderName string }
Modify NonceCacheCapacity and NonceCacheTimeout if your service needs to authenticate more than 5,000 requests per second. For example, if you need to handle 10,000 requests per second and timeout after one minute, you may want to set NonceCacheTimeout to 60 and NonceCacheCapacity to 10000 * cacheTimeout = 600000.
type NonceCache ¶
func NewNonceCache ¶
func NewNonceCache(capacity int, cacheTTL int, clock holster.Clock) *NonceCache
Return a new NonceCache. Allows you to control cache capacity and ttl
func (*NonceCache) InCache ¶
func (n *NonceCache) InCache(nonce string) bool
InCache checks if a nonce is in the cache. If not, it adds it to the cache and returns false. Otherwise it returns true.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Represents a service that can be used to sign and authenticate requests.
func New ¶
Return a new Service. Config can not be nil. If you need control over setting time and random providers, use NewWithProviders.
func NewWithProviders ¶
func NewWithProviders(config *Config, clock holster.Clock, randomProvider random.RandomProvider) (*Service, error)
Returns a new Service. Provides control over time and random providers.
func (*Service) AuthenticateRequest ¶
Authenticates HTTP request to ensure it was sent by an authorized sender.
Example ¶
package main import ( "fmt" "net/http" "os" "strings" "time" "github.com/mailgun/holster" "github.com/mailgun/holster/httpsign" "github.com/mailgun/holster/random" "github.com/mailgun/holster/secret" ) func main() { // For consistency during tests, OMIT THIS LINE IN PRODUCTION secret.RandomProvider = &random.FakeRNG{} // Create a new randomly generated key key, err := secret.NewKey() // Store the key on disk for retrieval later fd, err := os.Create("/tmp/test-secret.key") if err != nil { panic(err) } fd.Write([]byte(secret.KeyToEncodedString(key))) fd.Close() // When authenticating a request, the config must match that of the signing code auths, err := httpsign.New(&httpsign.Config{ // Our pre-generated shared key KeyPath: "/tmp/test-secret.key", // Include headers in the signed request HeadersToSign: []string{"X-Mailgun-Header"}, // Include the HTTP Verb and URI in the signed request SignVerbAndURI: true, // For consistency during tests, OMIT THESE 2 LINES IN PRODUCTION Clock: &holster.FrozenClock{CurrentTime: time.Unix(1330837567, 0)}, Random: &random.FakeRNG{}, }) if err != nil { panic(err) } // Pretend we received a new signed request r, _ := http.NewRequest("POST", "", strings.NewReader(`{"hello":"world"}`)) // Add our custom header that is included in the signature r.Header.Set("X-Mailgun-Header", "nyan-cat") // These are the fields set by the client signing the request r.Header.Set("X-Mailgun-Nonce", "000102030405060708090a0b0c0d0e0f") r.Header.Set("X-Mailgun-Timestamp", "1330837567") r.Header.Set("X-Mailgun-Signature", "33f589de065a81b671c9728e7c6b6fecfb94324cb10472f33dc1f78b2a9e4fee") r.Header.Set("X-Mailgun-Signature-Version", "2") // Verify the request err = auths.AuthenticateRequest(r) if err != nil { panic(err) } fmt.Printf("Request Verified\n") }
Output: Request Verified
func (*Service) AuthenticateRequestWithKey ¶
Authenticates HTTP request to ensure it was sent by an authorized sender. Checks message signature with the passed in key, not the one initialized with.
func (*Service) CheckTimestamp ¶
Parses a timestamp header and returns true if the timestamp is neither older than the TTL or is from the future.
func (*Service) SignRequest ¶
Signs a given HTTP request with signature, nonce, and timestamp.
Example ¶
package main import ( "fmt" "net/http" "os" "strings" "time" "github.com/mailgun/holster" "github.com/mailgun/holster/httpsign" "github.com/mailgun/holster/random" "github.com/mailgun/holster/secret" ) func main() { // For consistency during tests, OMIT THIS LINE IN PRODUCTION secret.RandomProvider = &random.FakeRNG{} // Create a new randomly generated key key, err := secret.NewKey() // Store the key on disk for retrieval later fd, err := os.Create("/tmp/test-secret.key") if err != nil { panic(err) } fd.Write([]byte(secret.KeyToEncodedString(key))) fd.Close() auths, err := httpsign.New(&httpsign.Config{ // Our pre-generated shared key KeyPath: "/tmp/test-secret.key", // Optionally include headers in the signed request HeadersToSign: []string{"X-Mailgun-Header"}, // Optionally include the HTTP Verb and URI in the signed request SignVerbAndURI: true, // For consistency during tests, OMIT THESE 2 LINES IN PRODUCTION Clock: &holster.FrozenClock{CurrentTime: time.Unix(1330837567, 0)}, Random: &random.FakeRNG{}, }) if err != nil { panic(err) } // Build new request r, _ := http.NewRequest("POST", "", strings.NewReader(`{"hello":"world"}`)) // Add our custom header that is included in the signature r.Header.Set("X-Mailgun-Header", "nyan-cat") // Sign the request err = auths.SignRequest(r) if err != nil { panic(err) } // Preform the request // client := &http.Client{} // response, _ := client.Do(r) fmt.Printf("%s: %s\n", httpsign.XMailgunNonce, r.Header.Get(httpsign.XMailgunNonce)) fmt.Printf("%s: %s\n", httpsign.XMailgunTimestamp, r.Header.Get(httpsign.XMailgunTimestamp)) fmt.Printf("%s: %s\n", httpsign.XMailgunSignature, r.Header.Get(httpsign.XMailgunSignature)) fmt.Printf("%s: %s\n", httpsign.XMailgunSignatureVersion, r.Header.Get(httpsign.XMailgunSignatureVersion)) }
Output: X-Mailgun-Nonce: 000102030405060708090a0b0c0d0e0f X-Mailgun-Timestamp: 1330837567 X-Mailgun-Signature: 33f589de065a81b671c9728e7c6b6fecfb94324cb10472f33dc1f78b2a9e4fee X-Mailgun-Signature-Version: 2