package oidcrp import ( "crypto/aes" "crypto/cipher" "crypto/hkdf" "crypto/rand" "crypto/sha256" "encoding/base64" "errors" "fmt" "strings" ) const ( TxnInfo = "tug txn v1" SessionInfo = "tug session v1" ) var ErrOpen = errors.New("oidcrp: sealed value rejected") type Sealer struct { aead cipher.AEAD } func NewSealer(master []byte, info string) (*Sealer, error) { if len(master) != 32 { return nil, errors.New("oidcrp: the master key is not 32 bytes") } key, err := hkdf.Key(sha256.New, master, nil, info, 32) if err != nil { return nil, fmt.Errorf("oidcrp: derive %q: %w", info, err) } block, err := aes.NewCipher(key) if err != nil { return nil, err } aead, err := cipher.NewGCM(block) if err != nil { return nil, err } return &Sealer{aead: aead}, nil } func (s *Sealer) Seal(name string, plaintext []byte) string { nonce := make([]byte, s.aead.NonceSize()) rand.Read(nonce) return base64.RawURLEncoding.EncodeToString(s.aead.Seal(nonce, nonce, plaintext, []byte(name))) } func outsideAlphabet(r rune) bool { return !(r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '-' || r == '_') } func (s *Sealer) Open(name, value string) ([]byte, error) { if strings.IndexFunc(value, outsideAlphabet) >= 0 { return nil, ErrOpen } b, err := base64.RawURLEncoding.Strict().DecodeString(value) n := s.aead.NonceSize() if err != nil || len(b) < n+s.aead.Overhead() { return nil, ErrOpen } plaintext, err := s.aead.Open(nil, b[:n], b[n:], []byte(name)) if err != nil { return nil, ErrOpen } return plaintext, nil }