// Package credential generates and verifies DWH installation credentials. package credential import ( "crypto/sha256" "crypto/subtle" "encoding/base64" "io" "strings" "github.com/aritmolab/thothii/tools/dwh-auth/internal/record" ) const Prefix = "thtdwh_v1" const KeyIDEncodedLength = 16 const SecretEncodedLength = 43 const MaxHeaderBytes = 128 type Material struct { Value []byte KeyID string Digest record.Digest } // Generate creates one canonical version 1 credential from random. func Generate(random io.Reader) (Material, error) { keyIDBytes := make([]byte, 12) secretBytes := make([]byte, 32) if _, err := io.ReadFull(random, keyIDBytes); err != nil { return Material{}, err } if _, err := io.ReadFull(random, secretBytes); err != nil { return Material{}, err } keyID := base64.RawURLEncoding.EncodeToString(keyIDBytes) secret := base64.RawURLEncoding.EncodeToString(secretBytes) value := []byte(Prefix + "." + keyID + "." + secret) sum := sha256.Sum256(value) return Material{Value: value, KeyID: keyID, Digest: record.Digest(sum)}, nil } // VerifyV1 authenticates one canonical version 1 credential and returns its key ID. func VerifyV1(value []byte, expected record.Digest) (string, bool) { if len(value) > MaxHeaderBytes || len(value) != len(Prefix)+1+KeyIDEncodedLength+1+SecretEncodedLength { return "", false } parts := strings.Split(string(value), ".") if len(parts) != 3 || parts[0] != Prefix || !canonicalBase64(parts[1], 12, KeyIDEncodedLength) || !canonicalBase64(parts[2], 32, SecretEncodedLength) { return "", false } sum := sha256.Sum256(value) if subtle.ConstantTimeCompare(sum[:], expected[:]) != 1 { return "", false } return parts[1], true } // VerifyLegacy authenticates an opaque legacy credential without interpreting its syntax. func VerifyLegacy(value []byte, expected record.Digest) bool { if len(value) < 1 || len(value) > MaxHeaderBytes { return false } for _, b := range value { if b <= 0x1f || b == 0x7f { return false } } sum := sha256.Sum256(value) return subtle.ConstantTimeCompare(sum[:], expected[:]) == 1 } func canonicalBase64(value string, decodedLength, encodedLength int) bool { if len(value) != encodedLength { return false } decoded, err := base64.RawURLEncoding.DecodeString(value) return err == nil && len(decoded) == decodedLength && base64.RawURLEncoding.EncodeToString(decoded) == value }