SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
2.6 KB · 79 lines go
Raw Blame History
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