spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1// Package signer implements the request signature of docs/PROBE-PROTOCOL.md § Authentication.2//3// canonical = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + sha256_hex(raw_body_bytes_as_sent)4// signature = lowercase hex HMAC-SHA256(key_bytes, canonical)5//6// PATH is the request path without query string (e.g. "/ingest/v1/batch"). For gzip bodies the hash covers7// the compressed bytes exactly as sent; for GET (no body) it is sha256_hex("").8package signer910import (11 "crypto/hmac"12 "crypto/sha256"13 "encoding/hex"14 "errors"15 "fmt"16 "net/http"17 "strconv"18)1920// Header names.21const (22 HeaderProbe = "X-IP-Probe"23 HeaderTimestamp = "X-IP-Timestamp"24 HeaderSignature = "X-IP-Signature"25)2627// Signer holds a probe identity and its decoded HMAC key.28type Signer struct {29 ProbeID string30 key []byte31}3233// New decodes the 64-hex-char key. Any even-length hex is accepted, but the server issues 32-byte keys.34func New(probeID, hexKey string) (*Signer, error) {35 if probeID == "" {36 return nil, errors.New("signer: empty probe_id")37 }38 key, err := hex.DecodeString(hexKey)39 if err != nil {40 return nil, fmt.Errorf("signer: key is not hex: %w", err)41 }42 if len(key) < 16 {43 return nil, fmt.Errorf("signer: key too short (%d bytes, want 32)", len(key))44 }45 return &Signer{ProbeID: probeID, key: key}, nil46}4748// BodyHash returns sha256_hex(body); body may be nil (GET).49func BodyHash(body []byte) string {50 sum := sha256.Sum256(body)51 return hex.EncodeToString(sum[:])52}5354// Canonical builds the string to sign.55func Canonical(method, path string, ts int64, body []byte) string {56 return method + "\n" + path + "\n" + strconv.FormatInt(ts, 10) + "\n" + BodyHash(body)57}5859// Sign returns the lowercase hex signature for the given request parameters.60func (s *Signer) Sign(method, path string, ts int64, body []byte) string {61 mac := hmac.New(sha256.New, s.key)62 mac.Write([]byte(Canonical(method, path, ts, body)))63 return hex.EncodeToString(mac.Sum(nil))64}6566// Apply sets the three authentication headers on req. body must be the exact bytes that will be sent67// (already gzip-compressed when Content-Encoding: gzip is used).68func (s *Signer) Apply(req *http.Request, ts int64, body []byte) {69 req.Header.Set(HeaderProbe, s.ProbeID)70 req.Header.Set(HeaderTimestamp, strconv.FormatInt(ts, 10))71 req.Header.Set(HeaderSignature, s.Sign(req.Method, req.URL.Path, ts, body))72}7374// Verify checks a signature in constant time (used by tests and handy for a Go-side server).75func (s *Signer) Verify(method, path string, ts int64, body []byte, sig string) bool {76 want := s.Sign(method, path, ts, body)77 return hmac.Equal([]byte(want), []byte(sig))78}79