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 }