Raw
 1package oidcrp
 2
 3import (
 4	"crypto/aes"
 5	"crypto/cipher"
 6	"crypto/hkdf"
 7	"crypto/rand"
 8	"crypto/sha256"
 9	"encoding/base64"
10	"errors"
11	"fmt"
12	"strings"
13)
14
15const (
16	TxnInfo     = "tug txn v1"
17	SessionInfo = "tug session v1"
18)
19
20var ErrOpen = errors.New("oidcrp: sealed value rejected")
21
22type Sealer struct {
23	aead cipher.AEAD
24}
25
26func NewSealer(master []byte, info string) (*Sealer, error) {
27	if len(master) != 32 {
28		return nil, errors.New("oidcrp: the master key is not 32 bytes")
29	}
30	key, err := hkdf.Key(sha256.New, master, nil, info, 32)
31	if err != nil {
32		return nil, fmt.Errorf("oidcrp: derive %q: %w", info, err)
33	}
34	block, err := aes.NewCipher(key)
35	if err != nil {
36		return nil, err
37	}
38	aead, err := cipher.NewGCM(block)
39	if err != nil {
40		return nil, err
41	}
42	return &Sealer{aead: aead}, nil
43}
44
45func (s *Sealer) Seal(name string, plaintext []byte) string {
46	nonce := make([]byte, s.aead.NonceSize())
47	rand.Read(nonce)
48	return base64.RawURLEncoding.EncodeToString(s.aead.Seal(nonce, nonce, plaintext, []byte(name)))
49}
50
51func outsideAlphabet(r rune) bool {
52	return !(r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '-' || r == '_')
53}
54
55func (s *Sealer) Open(name, value string) ([]byte, error) {
56	if strings.IndexFunc(value, outsideAlphabet) >= 0 {
57		return nil, ErrOpen
58	}
59	b, err := base64.RawURLEncoding.Strict().DecodeString(value)
60	n := s.aead.NonceSize()
61	if err != nil || len(b) < n+s.aead.Overhead() {
62		return nil, ErrOpen
63	}
64	plaintext, err := s.aead.Open(nil, b[:n], b[n:], []byte(name))
65	if err != nil {
66		return nil, ErrOpen
67	}
68	return plaintext, nil
69}