[core] Add validation and reset of encrypted values

in order to detect change of encryption pass

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2023-03-24 19:23:48 +01:00
parent 7126f6d7b7
commit 3213f4ac37
Signed by: luzifer
GPG Key ID: D91C3E91E4CAD6F5
4 changed files with 80 additions and 0 deletions

View File

@ -209,6 +209,13 @@ func (s Service) ListPermittedChannels() ([]string, error) {
return out, nil return out, nil
} }
func (s Service) RemoveAllExtendedTwitchCredentials() error {
return errors.Wrap(
s.db.DB().Delete(&extendedPermission{}, "1 = 1").Error,
"deleting data from table",
)
}
func (s Service) RemoveExendedTwitchCredentials(channel string) error { func (s Service) RemoveExendedTwitchCredentials(channel string) error {
return errors.Wrap( return errors.Wrap(
s.db.DB().Delete(&extendedPermission{}, "channel = ?", strings.TrimLeft(channel, "#")).Error, s.db.DB().Delete(&extendedPermission{}, "channel = ?", strings.TrimLeft(channel, "#")).Error,

17
main.go
View File

@ -198,6 +198,7 @@ func handleSubCommand(args []string) {
fmt.Println(" actor-docs Generate markdown documentation for available actors") fmt.Println(" actor-docs Generate markdown documentation for available actors")
fmt.Println(" api-token <name> <scope...> Generate an api-token to be entered into the config") fmt.Println(" api-token <name> <scope...> Generate an api-token to be entered into the config")
fmt.Println(" migrate-v2 <old file> Migrate old (*.json.gz) storage file into new database") fmt.Println(" migrate-v2 <old file> Migrate old (*.json.gz) storage file into new database")
fmt.Println(" reset-secrets Remove encrypted data to reset encryption passphrase")
fmt.Println(" validate-config Try to load configuration file and report errors if any") fmt.Println(" validate-config Try to load configuration file and report errors if any")
fmt.Println(" help Prints this help message") fmt.Println(" help Prints this help message")
@ -217,6 +218,18 @@ func handleSubCommand(args []string) {
log.Info("v2 storage file was migrated") log.Info("v2 storage file was migrated")
case "reset-secrets":
// Nuke permission table entries
if err := accessService.RemoveAllExtendedTwitchCredentials(); err != nil {
log.WithError(err).Fatal("resetting Twitch credentials")
}
log.Info("removed stored Twitch credentials")
if err := db.ResetEncryptedCoreMeta(); err != nil {
log.WithError(err).Fatal("resetting encrypted meta entries")
}
log.Info("removed encrypted meta entries")
case "validate-config": case "validate-config":
if err := loadConfig(cfg.Config); err != nil { if err := loadConfig(cfg.Config); err != nil {
log.WithError(err).Fatal("loading config") log.WithError(err).Fatal("loading config")
@ -303,6 +316,10 @@ func main() {
return return
} }
if err = db.ValidateEncryption(); err != nil {
log.WithError(err).Fatal("validation of database encryption failed, fix encryption passphrase or use 'twitch-bot reset-secrets' to wipe encrypted data")
}
if err = loadConfig(cfg.Config); err != nil { if err = loadConfig(cfg.Config); err != nil {
if os.IsNotExist(errors.Cause(err)) { if os.IsNotExist(errors.Cause(err)) {
if err = writeDefaultConfigFile(cfg.Config); err != nil { if err = writeDefaultConfigFile(cfg.Config); err != nil {

View File

@ -2,12 +2,23 @@ package database
import ( import (
"bytes" "bytes"
"crypto/sha512"
"encoding/json" "encoding/json"
"fmt"
"strings" "strings"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
"github.com/Luzifer/go_helpers/v2/backoff"
)
const (
encryptionValidationKey = "encryption-validation"
encryptionValidationMinBackoff = 500 * time.Millisecond
encryptionValidationTries = 5
) )
type ( type (
@ -47,12 +58,55 @@ func (c connector) ReadEncryptedCoreMeta(key string, value any) error {
return c.readCoreMeta(key, value, c.DecryptField) return c.readCoreMeta(key, value, c.DecryptField)
} }
// ResetEncryptedCoreMeta removes all CoreKV entries from the database
func (c connector) ResetEncryptedCoreMeta() error {
return errors.Wrap(
c.db.Delete(&coreKV{}, "value LIKE ?", "U2FsdGVkX1%").Error,
"removing encrypted meta entries",
)
}
// StoreEncryptedCoreMeta works like StoreCoreMeta but encrypts the // StoreEncryptedCoreMeta works like StoreCoreMeta but encrypts the
// marshalled value before storing it // marshalled value before storing it
func (c connector) StoreEncryptedCoreMeta(key string, value any) error { func (c connector) StoreEncryptedCoreMeta(key string, value any) error {
return c.storeCoreMeta(key, value, c.EncryptField) return c.storeCoreMeta(key, value, c.EncryptField)
} }
func (c connector) ValidateEncryption() error {
validationHasher := sha512.New()
fmt.Fprint(validationHasher, c.encryptionSecret)
var (
storedHash string
validationHash = fmt.Sprintf("%x", validationHasher.Sum(nil))
)
err := backoff.NewBackoff().
WithMaxIterations(encryptionValidationTries).
WithMinIterationTime(encryptionValidationMinBackoff).
Retry(func() error {
return c.ReadEncryptedCoreMeta(encryptionValidationKey, &storedHash)
})
switch {
case err == nil:
if storedHash != validationHash {
// Shouldn't happen: When decryption is possible it should match
return errors.New("mismatch between expected and stored hash")
}
return nil
case errors.Is(err, ErrCoreMetaNotFound):
return errors.Wrap(
c.StoreEncryptedCoreMeta(encryptionValidationKey, validationHash),
"initializing encryption validation",
)
default:
return errors.Wrap(err, "reading encryption-validation")
}
}
func (c connector) readCoreMeta(key string, value any, processor func(string) (string, error)) (err error) { func (c connector) readCoreMeta(key string, value any, processor func(string) (string, error)) (err error) {
var data coreKV var data coreKV

View File

@ -16,8 +16,10 @@ type (
ReadCoreMeta(key string, value any) error ReadCoreMeta(key string, value any) error
StoreCoreMeta(key string, value any) error StoreCoreMeta(key string, value any) error
ReadEncryptedCoreMeta(key string, value any) error ReadEncryptedCoreMeta(key string, value any) error
ResetEncryptedCoreMeta() error
StoreEncryptedCoreMeta(key string, value any) error StoreEncryptedCoreMeta(key string, value any) error
DecryptField(string) (string, error) DecryptField(string) (string, error)
EncryptField(string) (string, error) EncryptField(string) (string, error)
ValidateEncryption() error
} }
) )