carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package gate
2
3import (
4 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/base64"
7 "strconv"
8 "strings"
9 "time"
10)
11
12// The cookie is v1.<expiry unix>.<base64url(HMAC-SHA256(key, host "\n" expiry))>
13// with key derived from the stored bcrypt hash and nothing else. That is
14// what makes the design stateless and gives it its one rotation rule:
15// change or clear the password and every cookie minted under the old one
16// stops verifying at once, on every box, with no key to mint or mirror.
17// Anyone who can read the hash can mint a cookie — root on the box and a
18// bucket reader, who already hold the tenant's sealed secrets (spec §4.3).
19
20const cookieVersion = "v1"
21
22func cookieKey(hash string) []byte {
23 m := hmac.New(sha256.New, []byte("carlos-gate-cookie-v1"))
24 m.Write([]byte(hash))
25 return m.Sum(nil)
26}
27
28func cookieMAC(key []byte, host string, expiry int64) []byte {
29 m := hmac.New(sha256.New, key)
30 m.Write([]byte(host))
31 m.Write([]byte{'\n'})
32 m.Write([]byte(strconv.FormatInt(expiry, 10)))
33 return m.Sum(nil)
34}
35
36// Mint produces a cookie value for host, valid until expiry.
37func Mint(host, hash string, expiry time.Time) string {
38 exp := expiry.Unix()
39 mac := cookieMAC(cookieKey(hash), host, exp)
40 return cookieVersion + "." + strconv.FormatInt(exp, 10) + "." + base64.RawURLEncoding.EncodeToString(mac)
41}
42
43// Verify reports whether value was minted for host under hash and has not
44// expired at now. One HMAC and a constant-time compare; no bcrypt.
45func Verify(value, host, hash string, now time.Time) bool {
46 parts := strings.Split(value, ".")
47 if len(parts) != 3 || parts[0] != cookieVersion {
48 return false
49 }
50 exp, err := strconv.ParseInt(parts[1], 10, 64)
51 // One spelling per expiry: ParseInt accepts "+123" and "-0", which
52 // would MAC differently from what Mint wrote for the same instant.
53 if err != nil || parts[1] != strconv.FormatInt(exp, 10) || exp <= now.Unix() {
54 return false
55 }
56 got, err := base64.RawURLEncoding.DecodeString(parts[2])
57 if err != nil {
58 return false
59 }
60 return hmac.Equal(got, cookieMAC(cookieKey(hash), host, exp))
61}
62