signing.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. package acn
  2. /*
  3. ArozOS Cluster Node protocol (ACN) - request signing
  4. Node-to-node requests do not use user sessions. Every request carries a
  5. set of headers that identify the sending node and an Ed25519 signature
  6. over the request method, target, cluster, node, timestamp, nonce and body
  7. hash. Receivers look the sender up in the membership list (through a
  8. PeerResolver), check the clock skew, reject replayed nonces and verify the
  9. signature with the sender's published public key.
  10. Relayed requests (see RelayPrefix) are signed over the target path with the
  11. relay prefix removed, so both the relaying node and the final node can
  12. verify the very same signature.
  13. */
  14. import (
  15. "bytes"
  16. "crypto/ed25519"
  17. "crypto/rand"
  18. "crypto/sha256"
  19. "encoding/base64"
  20. "encoding/hex"
  21. "errors"
  22. "io"
  23. "net/http"
  24. "strconv"
  25. "strings"
  26. "sync"
  27. "time"
  28. )
  29. const (
  30. HeaderNode = "X-Aroz-Node"
  31. HeaderCluster = "X-Aroz-Cluster"
  32. HeaderTimestamp = "X-Aroz-Timestamp"
  33. HeaderNonce = "X-Aroz-Nonce"
  34. HeaderSignature = "X-Aroz-Signature"
  35. // BasePath is the URL prefix every ACN endpoint lives under on each node.
  36. BasePath = "/cluster/acn"
  37. // RelayPrefix is the prefix used to ask a node to forward a request to a
  38. // node that is only reachable through a tunnel terminating at that node.
  39. // The full form is RelayPrefix + "/" + targetNodeID + originalPath.
  40. RelayPrefix = BasePath + "/relay"
  41. // MaxClockSkew is the accepted difference between sender and receiver clocks.
  42. MaxClockSkew = 5 * time.Minute
  43. // MaxSignedBody limits how much of a request body a verifier will buffer.
  44. MaxSignedBody = 64 << 20
  45. )
  46. var (
  47. ErrNotSigned = errors.New("request is not signed")
  48. ErrUnknownNode = errors.New("sender is not a member of this cluster")
  49. ErrWrongCluster = errors.New("request targets another cluster")
  50. ErrClockSkew = errors.New("request timestamp outside the accepted window")
  51. ErrReplay = errors.New("request nonce already seen")
  52. ErrBadSignature = errors.New("request signature invalid")
  53. ErrBodyTooLarge = errors.New("request body exceeds signed body limit")
  54. ErrPeerNoEndpoint = errors.New("peer has no reachable endpoint")
  55. )
  56. // Peer is the transport-level view of a cluster member.
  57. type Peer struct {
  58. ID string
  59. Name string
  60. PublicKey ed25519.PublicKey
  61. AdvertiseURL string //Base URL where the peer's HTTP server can be reached, empty for NAT-only nodes
  62. TunnelVia string //Node ID that terminates the peer's tunnel when it has no AdvertiseURL
  63. }
  64. // PeerResolver is implemented by the membership layer so the transport can
  65. // look up public keys and endpoints without importing it.
  66. type PeerResolver interface {
  67. ResolvePeer(nodeID string) (*Peer, bool)
  68. }
  69. // Signer produces the ACN headers for outgoing requests.
  70. type Signer struct {
  71. NodeID string
  72. ClusterID string
  73. Key *NodeKey
  74. }
  75. // SignedIdentity is what a verifier learns about a valid request.
  76. type SignedIdentity struct {
  77. NodeID string
  78. ClusterID string
  79. Peer *Peer
  80. }
  81. // canonicalString builds the byte string that is signed and verified.
  82. func canonicalString(method, target, clusterID, nodeID, timestamp, nonce string, bodyHash []byte) []byte {
  83. var b bytes.Buffer
  84. b.WriteString(strings.ToUpper(method))
  85. b.WriteByte('\n')
  86. b.WriteString(target)
  87. b.WriteByte('\n')
  88. b.WriteString(clusterID)
  89. b.WriteByte('\n')
  90. b.WriteString(nodeID)
  91. b.WriteByte('\n')
  92. b.WriteString(timestamp)
  93. b.WriteByte('\n')
  94. b.WriteString(nonce)
  95. b.WriteByte('\n')
  96. b.WriteString(hex.EncodeToString(bodyHash))
  97. return b.Bytes()
  98. }
  99. // SignedTarget returns the path (plus query when present) a request is signed over.
  100. func SignedTarget(path string, rawQuery string) string {
  101. if rawQuery == "" {
  102. return path
  103. }
  104. return path + "?" + rawQuery
  105. }
  106. // StripRelayPrefix removes the relay routing prefix from a request path and
  107. // returns the target node ID and the original path. ok is false when the path
  108. // is not a relay path.
  109. func StripRelayPrefix(path string) (targetNode string, originalPath string, ok bool) {
  110. if !strings.HasPrefix(path, RelayPrefix+"/") {
  111. return "", path, false
  112. }
  113. rest := strings.TrimPrefix(path, RelayPrefix+"/")
  114. idx := strings.Index(rest, "/")
  115. if idx <= 0 {
  116. return "", path, false
  117. }
  118. return rest[:idx], rest[idx:], true
  119. }
  120. func newNonce() string {
  121. buf := make([]byte, 16)
  122. rand.Read(buf)
  123. return hex.EncodeToString(buf)
  124. }
  125. // Sign adds the ACN authentication headers to req. target must be the path
  126. // (and query) the receiver will verify against, which for relayed requests is
  127. // the path without the relay prefix. body is the full request body.
  128. func (s *Signer) Sign(req *http.Request, target string, body []byte) {
  129. s.SignAt(req, target, body, time.Now())
  130. }
  131. // SignAt is Sign with an explicit timestamp, used by tests.
  132. func (s *Signer) SignAt(req *http.Request, target string, body []byte, at time.Time) {
  133. ts := strconv.FormatInt(at.Unix(), 10)
  134. nonce := newNonce()
  135. sum := sha256.Sum256(body)
  136. msg := canonicalString(req.Method, target, s.ClusterID, s.NodeID, ts, nonce, sum[:])
  137. sig := ed25519.Sign(s.Key.Private, msg)
  138. req.Header.Set(HeaderNode, s.NodeID)
  139. req.Header.Set(HeaderCluster, s.ClusterID)
  140. req.Header.Set(HeaderTimestamp, ts)
  141. req.Header.Set(HeaderNonce, nonce)
  142. req.Header.Set(HeaderSignature, base64.StdEncoding.EncodeToString(sig))
  143. }
  144. // nonceCache remembers recently seen nonces for replay protection.
  145. type nonceCache struct {
  146. mu sync.Mutex
  147. seen map[string]int64 //nonce -> expiry unix time
  148. lastGC int64
  149. ttl int64
  150. maxSize int
  151. }
  152. func newNonceCache(ttl time.Duration) *nonceCache {
  153. return &nonceCache{
  154. seen: map[string]int64{},
  155. ttl: int64(ttl.Seconds()),
  156. maxSize: 100000,
  157. }
  158. }
  159. // remember returns false when the nonce was already recorded.
  160. func (c *nonceCache) remember(nonce string, now int64) bool {
  161. c.mu.Lock()
  162. defer c.mu.Unlock()
  163. if now-c.lastGC > 60 || len(c.seen) > c.maxSize {
  164. for k, exp := range c.seen {
  165. if exp < now {
  166. delete(c.seen, k)
  167. }
  168. }
  169. c.lastGC = now
  170. }
  171. if _, exists := c.seen[nonce]; exists {
  172. return false
  173. }
  174. c.seen[nonce] = now + c.ttl
  175. return true
  176. }
  177. // Verifier checks incoming ACN requests.
  178. type Verifier struct {
  179. ClusterID func() string //Returns the cluster this node belongs to, empty when not in a cluster
  180. Resolver PeerResolver
  181. nonces *nonceCache
  182. Now func() time.Time
  183. }
  184. // NewVerifier creates a verifier backed by the given resolver.
  185. func NewVerifier(clusterID func() string, resolver PeerResolver) *Verifier {
  186. return &Verifier{
  187. ClusterID: clusterID,
  188. Resolver: resolver,
  189. nonces: newNonceCache(MaxClockSkew * 2),
  190. Now: time.Now,
  191. }
  192. }
  193. // ReadBody buffers and restores a request body so it can be hashed and then
  194. // consumed by the handler.
  195. func ReadBody(r *http.Request) ([]byte, error) {
  196. if r.Body == nil {
  197. return []byte{}, nil
  198. }
  199. body, err := io.ReadAll(io.LimitReader(r.Body, MaxSignedBody+1))
  200. if err != nil {
  201. return nil, err
  202. }
  203. if len(body) > MaxSignedBody {
  204. return nil, ErrBodyTooLarge
  205. }
  206. r.Body.Close()
  207. r.Body = io.NopCloser(bytes.NewReader(body))
  208. return body, nil
  209. }
  210. // Verify validates the ACN headers of r against target (the signed path) and
  211. // body and returns the identity of the sender. The request body is left
  212. // readable for the handler.
  213. func (v *Verifier) Verify(r *http.Request, target string, body []byte) (*SignedIdentity, error) {
  214. nodeID := r.Header.Get(HeaderNode)
  215. clusterID := r.Header.Get(HeaderCluster)
  216. ts := r.Header.Get(HeaderTimestamp)
  217. nonce := r.Header.Get(HeaderNonce)
  218. sigEnc := r.Header.Get(HeaderSignature)
  219. if nodeID == "" || clusterID == "" || ts == "" || nonce == "" || sigEnc == "" {
  220. return nil, ErrNotSigned
  221. }
  222. localCluster := v.ClusterID()
  223. if localCluster == "" || localCluster != clusterID {
  224. return nil, ErrWrongCluster
  225. }
  226. tsInt, err := strconv.ParseInt(ts, 10, 64)
  227. if err != nil {
  228. return nil, ErrClockSkew
  229. }
  230. now := v.Now().Unix()
  231. skew := now - tsInt
  232. if skew < 0 {
  233. skew = -skew
  234. }
  235. if skew > int64(MaxClockSkew.Seconds()) {
  236. return nil, ErrClockSkew
  237. }
  238. peer, ok := v.Resolver.ResolvePeer(nodeID)
  239. if !ok || peer == nil || len(peer.PublicKey) != ed25519.PublicKeySize {
  240. return nil, ErrUnknownNode
  241. }
  242. sig, err := base64.StdEncoding.DecodeString(sigEnc)
  243. if err != nil {
  244. return nil, ErrBadSignature
  245. }
  246. sum := sha256.Sum256(body)
  247. msg := canonicalString(r.Method, target, clusterID, nodeID, ts, nonce, sum[:])
  248. if !ed25519.Verify(peer.PublicKey, msg, sig) {
  249. return nil, ErrBadSignature
  250. }
  251. //Signature is valid, only now spend the nonce so bad requests cannot poison the cache
  252. if !v.nonces.remember(nodeID+":"+nonce, now) {
  253. return nil, ErrReplay
  254. }
  255. return &SignedIdentity{NodeID: nodeID, ClusterID: clusterID, Peer: peer}, nil
  256. }
  257. // VerifyRequest is the common entry point for HTTP handlers: it buffers the
  258. // body, derives the signed target (stripping the relay prefix when present)
  259. // and verifies the request.
  260. func (v *Verifier) VerifyRequest(r *http.Request) (*SignedIdentity, []byte, error) {
  261. body, err := ReadBody(r)
  262. if err != nil {
  263. return nil, nil, err
  264. }
  265. path := r.URL.Path
  266. if _, original, ok := StripRelayPrefix(path); ok {
  267. path = original
  268. }
  269. identity, err := v.Verify(r, SignedTarget(path, r.URL.RawQuery), body)
  270. if err != nil {
  271. return nil, nil, err
  272. }
  273. return identity, body, nil
  274. }