[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
}
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 {
return errors.Wrap(
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(" 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(" 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(" help Prints this help message")
@ -217,6 +218,18 @@ func handleSubCommand(args []string) {
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":
if err := loadConfig(cfg.Config); err != nil {
log.WithError(err).Fatal("loading config")
@ -303,6 +316,10 @@ func main() {
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 os.IsNotExist(errors.Cause(err)) {
if err = writeDefaultConfigFile(cfg.Config); err != nil {

View file

@ -2,12 +2,23 @@ package database
import (
"bytes"
"crypto/sha512"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/pkg/errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/Luzifer/go_helpers/v2/backoff"
)
const (
encryptionValidationKey = "encryption-validation"
encryptionValidationMinBackoff = 500 * time.Millisecond
encryptionValidationTries = 5
)
type (
@ -47,12 +58,55 @@ func (c connector) ReadEncryptedCoreMeta(key string, value any) error {
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
// marshalled value before storing it
func (c connector) StoreEncryptedCoreMeta(key string, value any) error {
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) {
var data coreKV

View file

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