clavitor/clavis/clavis-vault/_old/lib/token.go

117 lines
2.7 KiB
Go

package lib
import (
"crypto/rand"
"crypto/sha256"
"fmt"
"math/big"
"strings"
)
const (
tokenPrefix = "cvt_"
tokenBytes = 32 // 8 bytes L1 + 24 bytes random
)
// base62 alphabet (digits + lowercase + uppercase)
const base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// MintToken creates a new agent bearer token embedding the L1 key.
// Returns the raw token (shown once) and its sha256 hex hash (stored in DB).
// Token format: cvt_ + base62(l1Raw[8] + random[24])
func MintToken(l1Raw []byte) (raw string, hash string) {
buf := make([]byte, tokenBytes)
copy(buf[:8], l1Raw)
rand.Read(buf[8:])
raw = tokenPrefix + base62Encode(buf)
hash = HashToken(raw)
return
}
// ParseToken extracts the L1 key (8 bytes, raw) from a cvt_ bearer token.
// Returns l1Raw and the token hash for agent lookup.
func ParseToken(raw string) (l1Raw []byte, hash string, err error) {
if !strings.HasPrefix(raw, tokenPrefix) {
return nil, "", fmt.Errorf("missing cvt_ prefix")
}
decoded, err := base62Decode(strings.TrimPrefix(raw, tokenPrefix))
if err != nil {
return nil, "", fmt.Errorf("invalid token encoding: %w", err)
}
if len(decoded) != tokenBytes {
return nil, "", fmt.Errorf("invalid token length: got %d, want %d", len(decoded), tokenBytes)
}
l1Raw = decoded[:8]
hash = HashToken(raw)
return
}
// HashToken returns the sha256 hex digest of a raw token string.
func HashToken(raw string) string {
h := sha256.Sum256([]byte(raw))
return fmt.Sprintf("%x", h)
}
// base62Encode encodes bytes as a base62 string.
func base62Encode(data []byte) string {
n := new(big.Int).SetBytes(data)
base := big.NewInt(62)
zero := big.NewInt(0)
mod := new(big.Int)
var chars []byte
for n.Cmp(zero) > 0 {
n.DivMod(n, base, mod)
chars = append(chars, base62Chars[mod.Int64()])
}
// Preserve leading zeros
for _, b := range data {
if b != 0 {
break
}
chars = append(chars, base62Chars[0])
}
// Reverse
for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 {
chars[i], chars[j] = chars[j], chars[i]
}
return string(chars)
}
// base62Decode decodes a base62 string back to bytes.
func base62Decode(s string) ([]byte, error) {
n := new(big.Int)
base := big.NewInt(62)
for _, c := range s {
idx := strings.IndexRune(base62Chars, c)
if idx < 0 {
return nil, fmt.Errorf("invalid base62 character: %c", c)
}
n.Mul(n, base)
n.Add(n, big.NewInt(int64(idx)))
}
// Convert to fixed-size byte slice
b := n.Bytes()
// Count leading zeros in the encoded string
leadingZeros := 0
for _, c := range s {
if c == rune(base62Chars[0]) {
leadingZeros++
} else {
break
}
}
// Prepend zero bytes
result := make([]byte, leadingZeros+len(b))
copy(result[leadingZeros:], b)
return result, nil
}