1package web
2
3import (
4 "crypto/hkdf"
5 "crypto/hmac"
6 "crypto/sha256"
7 "encoding/base64"
8 "errors"
9 "strings"
10)
11
12var errSeal = errors.New("sealed value rejected")
13
14func deriveKey(master []byte, info string) (*[]byte, error) {
15 k, err := hkdf.Key(sha256.New, master, nil, info, 32)
16 if err != nil {
17 return nil, err
18 }
19 return &k, nil
20}
21
22func mac(key *[]byte, parts ...[]byte) []byte {
23 m := hmac.New(sha256.New, *key)
24 for _, p := range parts {
25 m.Write(p)
26 }
27 return m.Sum(nil)
28}
29
30func seal(key *[]byte, payload []byte) string {
31 enc := base64.RawURLEncoding
32 return enc.EncodeToString(payload) + "." + enc.EncodeToString(mac(key, payload))
33}
34
35func unseal(key *[]byte, v string) ([]byte, error) {
36 p64, t64, ok := strings.Cut(v, ".")
37 if !ok {
38 return nil, errSeal
39 }
40 enc := base64.RawURLEncoding.Strict()
41 payload, err1 := enc.DecodeString(p64)
42 tag, err2 := enc.DecodeString(t64)
43 if err1 != nil || err2 != nil || !hmac.Equal(mac(key, payload), tag) {
44 return nil, errSeal
45 }
46 return payload, nil
47}