Compare commits
3 Commits
ci/make-ch
...
9815b8e265
| Author | SHA1 | Date | |
|---|---|---|---|
| 9815b8e265 | |||
|
|
4c7ed3fc51 | ||
|
|
2a4ceb2045 |
68
internal/secret/derivation_index_test.go
Normal file
68
internal/secret/derivation_index_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package secret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/awnumar/memguard"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"git.eeqj.de/sneak/secret/pkg/agehd"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mockVault implements VaultInterface for testing getLongTermPrivateKey
|
||||||
|
type mockVaultForDerivation struct {
|
||||||
|
dir string
|
||||||
|
fs afero.Fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockVaultForDerivation) GetDirectory() (string, error) { return m.dir, nil }
|
||||||
|
func (m *mockVaultForDerivation) GetName() string { return "test-vault" }
|
||||||
|
func (m *mockVaultForDerivation) GetFilesystem() afero.Fs { return m.fs }
|
||||||
|
|
||||||
|
// Stubs for unused interface methods
|
||||||
|
func (m *mockVaultForDerivation) AddSecret(name string, value *memguard.LockedBuffer, force bool) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *mockVaultForDerivation) GetCurrentUnlocker() (Unlocker, error) { return nil, nil }
|
||||||
|
func (m *mockVaultForDerivation) CreatePassphraseUnlocker(passphrase *memguard.LockedBuffer) (*PassphraseUnlocker, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetLongTermPrivateKeyUsesVaultDerivationIndex(t *testing.T) {
|
||||||
|
testMnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||||
|
|
||||||
|
// Derive keys at index 0 and index 5 — they must differ
|
||||||
|
key0, err := agehd.DeriveIdentity(testMnemonic, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
key5, err := agehd.DeriveIdentity(testMnemonic, 5)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEqual(t, key0.String(), key5.String(), "different derivation indices must produce different keys")
|
||||||
|
|
||||||
|
// Set up in-memory filesystem with vault metadata at index 5
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
vaultDir := "/test-vault"
|
||||||
|
require.NoError(t, fs.MkdirAll(vaultDir, 0o700))
|
||||||
|
|
||||||
|
metadata := VaultMetadata{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
DerivationIndex: 5,
|
||||||
|
}
|
||||||
|
metaBytes, err := json.Marshal(metadata)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, afero.WriteFile(fs, vaultDir+"/vault-metadata.json", metaBytes, 0o600))
|
||||||
|
|
||||||
|
// Set the mnemonic env var
|
||||||
|
t.Setenv(EnvMnemonic, testMnemonic)
|
||||||
|
|
||||||
|
vault := &mockVaultForDerivation{dir: vaultDir, fs: fs}
|
||||||
|
result, err := getLongTermPrivateKey(fs, vault)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer result.Destroy()
|
||||||
|
|
||||||
|
// The derived key should match index 5, NOT index 0
|
||||||
|
assert.Equal(t, key5.String(), string(result.Bytes()), "getLongTermPrivateKey should use vault's derivation index (5), not hardcoded 0")
|
||||||
|
assert.NotEqual(t, key0.String(), string(result.Bytes()), "getLongTermPrivateKey must not use hardcoded index 0")
|
||||||
|
}
|
||||||
@@ -251,8 +251,25 @@ func getLongTermPrivateKey(fs afero.Fs, vault VaultInterface) (*memguard.LockedB
|
|||||||
// Check if mnemonic is available in environment variable
|
// Check if mnemonic is available in environment variable
|
||||||
envMnemonic := os.Getenv(EnvMnemonic)
|
envMnemonic := os.Getenv(EnvMnemonic)
|
||||||
if envMnemonic != "" {
|
if envMnemonic != "" {
|
||||||
// Use mnemonic directly to derive long-term key
|
// Read vault metadata to get the correct derivation index
|
||||||
ltIdentity, err := agehd.DeriveIdentity(envMnemonic, 0)
|
vaultDir, err := vault.GetDirectory()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get vault directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataPath := filepath.Join(vaultDir, "vault-metadata.json")
|
||||||
|
metadataBytes, err := afero.ReadFile(fs, metadataPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read vault metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata VaultMetadata
|
||||||
|
if err := json.Unmarshal(metadataBytes, &metadata); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse vault metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use mnemonic with the vault's actual derivation index
|
||||||
|
ltIdentity, err := agehd.DeriveIdentity(envMnemonic, metadata.DerivationIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to derive long-term key from mnemonic: %w", err)
|
return nil, fmt.Errorf("failed to derive long-term key from mnemonic: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user