Files
keepassgo/internal/vault/security.go
T
2026-04-09 06:42:21 -07:00

101 lines
3.0 KiB
Go

package vault
import (
"fmt"
"slices"
"github.com/tobischo/gokeepasslib/v3"
)
type SecuritySettings struct {
Cipher string
KDF string
}
const (
CipherAES256 = "aes256"
CipherChaCha20 = "chacha20"
KDFAES = "aes-kdf"
KDFArgon2 = "argon2"
)
func SupportedSecuritySettings() (ciphers []string, kdfs []string) {
return []string{CipherAES256, CipherChaCha20}, []string{KDFAES, KDFArgon2}
}
func DetectSecuritySettings(config *KDBXConfig) SecuritySettings {
settings := SecuritySettings{
Cipher: CipherChaCha20,
KDF: KDFArgon2,
}
if config == nil || config.Header == nil || config.Header.FileHeaders == nil {
return settings
}
if slices.Equal(config.Header.FileHeaders.CipherID, gokeepasslib.CipherAES) {
settings.Cipher = CipherAES256
}
if config.Header.FileHeaders.KdfParameters != nil && slices.Equal(config.Header.FileHeaders.KdfParameters.UUID, gokeepasslib.KdfAES4) {
settings.KDF = KDFAES
}
return settings
}
func NewSecurityConfig(settings SecuritySettings) (*KDBXConfig, error) {
db := gokeepasslib.NewDatabase(gokeepasslib.WithDatabaseKDBXVersion4())
config := &KDBXConfig{
Header: cloneHeader(db.Header),
InnerHeader: cloneInnerHeader(db.Content.InnerHeader),
}
return ApplySecuritySettings(config, settings)
}
func ApplySecuritySettings(config *KDBXConfig, settings SecuritySettings) (*KDBXConfig, error) {
if config == nil || config.Header == nil || config.Header.FileHeaders == nil {
return NewSecurityConfig(settings)
}
out := &KDBXConfig{
Header: cloneHeader(config.Header),
InnerHeader: cloneInnerHeader(config.InnerHeader),
}
if out.Header.FileHeaders.KdfParameters == nil {
defaults := gokeepasslib.NewDatabase(gokeepasslib.WithDatabaseKDBXVersion4())
out.Header.FileHeaders.KdfParameters = cloneHeader(defaults.Header).FileHeaders.KdfParameters
}
switch settings.Cipher {
case "", CipherChaCha20:
out.Header.FileHeaders.CipherID = slices.Clone(gokeepasslib.CipherChaCha20)
out.Header.FileHeaders.EncryptionIV = randomBytes(12)
case CipherAES256:
out.Header.FileHeaders.CipherID = slices.Clone(gokeepasslib.CipherAES)
out.Header.FileHeaders.EncryptionIV = randomBytes(16)
default:
return nil, fmt.Errorf("unsupported cipher %q", settings.Cipher)
}
var salt [32]byte
copy(salt[:], randomBytes(32))
switch settings.KDF {
case "", KDFArgon2:
defaults := gokeepasslib.NewDatabase(gokeepasslib.WithDatabaseKDBXVersion4())
kdf := defaults.Header.FileHeaders.KdfParameters
out.Header.FileHeaders.KdfParameters = &gokeepasslib.KdfParameters{
UUID: slices.Clone(gokeepasslib.KdfArgon2),
Rounds: kdf.Rounds,
Salt: salt,
Parallelism: kdf.Parallelism,
Memory: kdf.Memory,
Iterations: kdf.Iterations,
Version: kdf.Version,
}
case KDFAES:
out.Header.FileHeaders.KdfParameters = &gokeepasslib.KdfParameters{
UUID: slices.Clone(gokeepasslib.KdfAES4),
Rounds: 6000,
Salt: salt,
}
default:
return nil, fmt.Errorf("unsupported KDF %q", settings.KDF)
}
return out, nil
}