When removing a keychain unlocker, if the keychain item doesn't exist (e.g., already manually deleted or vault synced from another machine), the removal should still succeed since the goal is to remove the unlocker and the keychain item being gone already satisfies that goal.
185 lines
5.6 KiB
Go
185 lines
5.6 KiB
Go
//go:build darwin
|
|
// +build darwin
|
|
|
|
package secret
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/awnumar/memguard"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestKeychainStoreRetrieveDelete(t *testing.T) {
|
|
// Skip test if not on macOS
|
|
if runtime.GOOS != "darwin" {
|
|
t.Skip("Keychain tests only run on macOS")
|
|
}
|
|
|
|
// Test data
|
|
testItemName := "test-secret-keychain-item"
|
|
testData := "test-secret-data-12345"
|
|
testBuffer := memguard.NewBufferFromBytes([]byte(testData))
|
|
defer testBuffer.Destroy()
|
|
|
|
// Clean up any existing item first
|
|
_ = deleteFromKeychain(testItemName)
|
|
|
|
// Test 1: Store data in keychain
|
|
err := storeInKeychain(testItemName, testBuffer)
|
|
require.NoError(t, err, "Failed to store data in keychain")
|
|
|
|
// Test 2: Retrieve data from keychain
|
|
retrievedData, err := retrieveFromKeychain(testItemName)
|
|
require.NoError(t, err, "Failed to retrieve data from keychain")
|
|
assert.Equal(t, testData, string(retrievedData), "Retrieved data doesn't match stored data")
|
|
|
|
// Test 3: Update existing item (store again with different data)
|
|
newTestData := "updated-test-data-67890"
|
|
newTestBuffer := memguard.NewBufferFromBytes([]byte(newTestData))
|
|
defer newTestBuffer.Destroy()
|
|
|
|
err = storeInKeychain(testItemName, newTestBuffer)
|
|
require.NoError(t, err, "Failed to update data in keychain")
|
|
|
|
// Verify updated data
|
|
retrievedData, err = retrieveFromKeychain(testItemName)
|
|
require.NoError(t, err, "Failed to retrieve updated data from keychain")
|
|
assert.Equal(t, newTestData, string(retrievedData), "Retrieved data doesn't match updated data")
|
|
|
|
// Test 4: Delete from keychain
|
|
err = deleteFromKeychain(testItemName)
|
|
require.NoError(t, err, "Failed to delete data from keychain")
|
|
|
|
// Test 5: Verify item is deleted (should fail to retrieve)
|
|
_, err = retrieveFromKeychain(testItemName)
|
|
assert.Error(t, err, "Expected error when retrieving deleted item")
|
|
}
|
|
|
|
func TestKeychainInvalidItemName(t *testing.T) {
|
|
// Skip test if not on macOS
|
|
if runtime.GOOS != "darwin" {
|
|
t.Skip("Keychain tests only run on macOS")
|
|
}
|
|
|
|
testData := memguard.NewBufferFromBytes([]byte("test"))
|
|
defer testData.Destroy()
|
|
|
|
// Test invalid item names
|
|
invalidNames := []string{
|
|
"", // Empty name
|
|
"test space", // Contains space
|
|
"test/slash", // Contains slash
|
|
"test\\backslash", // Contains backslash
|
|
"test:colon", // Contains colon
|
|
"test;semicolon", // Contains semicolon
|
|
"test|pipe", // Contains pipe
|
|
"test@at", // Contains @
|
|
"test#hash", // Contains #
|
|
"test$dollar", // Contains $
|
|
"test&ersand", // Contains &
|
|
"test*asterisk", // Contains *
|
|
"test?question", // Contains ?
|
|
"test!exclamation", // Contains !
|
|
"test'quote", // Contains single quote
|
|
"test\"doublequote", // Contains double quote
|
|
"test(paren", // Contains parenthesis
|
|
"test[bracket", // Contains bracket
|
|
}
|
|
|
|
for _, name := range invalidNames {
|
|
err := storeInKeychain(name, testData)
|
|
assert.Error(t, err, "Expected error for invalid name: %s", name)
|
|
assert.Contains(t, err.Error(), "invalid keychain item name", "Error should mention invalid name for: %s", name)
|
|
}
|
|
|
|
// Test valid names (should not error on validation)
|
|
validNames := []string{
|
|
"test-name",
|
|
"test_name",
|
|
"test.name",
|
|
"TestName123",
|
|
"TEST_NAME_123",
|
|
"com.example.test",
|
|
"secret-vault-hostname-2024-01-01",
|
|
}
|
|
|
|
for _, name := range validNames {
|
|
err := validateKeychainItemName(name)
|
|
assert.NoError(t, err, "Expected no error for valid name: %s", name)
|
|
// Clean up
|
|
_ = deleteFromKeychain(name)
|
|
}
|
|
}
|
|
|
|
func TestKeychainNilData(t *testing.T) {
|
|
// Skip test if not on macOS
|
|
if runtime.GOOS != "darwin" {
|
|
t.Skip("Keychain tests only run on macOS")
|
|
}
|
|
|
|
// Test storing nil data
|
|
err := storeInKeychain("test-item", nil)
|
|
assert.Error(t, err, "Expected error when storing nil data")
|
|
assert.Contains(t, err.Error(), "data buffer is nil")
|
|
}
|
|
|
|
func TestKeychainLargeData(t *testing.T) {
|
|
// Skip test if not on macOS
|
|
if runtime.GOOS != "darwin" {
|
|
t.Skip("Keychain tests only run on macOS")
|
|
}
|
|
|
|
// Test with larger hex-encoded data (512 bytes of binary data = 1KB hex)
|
|
largeData := make([]byte, 512)
|
|
for i := range largeData {
|
|
largeData[i] = byte(i % 256)
|
|
}
|
|
|
|
// Convert to hex string for storage
|
|
hexData := hex.EncodeToString(largeData)
|
|
|
|
testItemName := "test-large-data"
|
|
testBuffer := memguard.NewBufferFromBytes([]byte(hexData))
|
|
defer testBuffer.Destroy()
|
|
|
|
// Clean up first
|
|
_ = deleteFromKeychain(testItemName)
|
|
|
|
// Store hex data
|
|
err := storeInKeychain(testItemName, testBuffer)
|
|
require.NoError(t, err, "Failed to store large data")
|
|
|
|
// Retrieve and verify
|
|
retrievedData, err := retrieveFromKeychain(testItemName)
|
|
require.NoError(t, err, "Failed to retrieve large data")
|
|
|
|
// Decode hex and compare
|
|
decodedData, err := hex.DecodeString(string(retrievedData))
|
|
require.NoError(t, err, "Failed to decode hex data")
|
|
assert.Equal(t, largeData, decodedData, "Large data mismatch")
|
|
|
|
// Clean up
|
|
_ = deleteFromKeychain(testItemName)
|
|
}
|
|
|
|
func TestDeleteNonExistentKeychainItem(t *testing.T) {
|
|
// Skip test if not on macOS
|
|
if runtime.GOOS != "darwin" {
|
|
t.Skip("Keychain tests only run on macOS")
|
|
}
|
|
|
|
// Ensure item doesn't exist
|
|
testItemName := "test-nonexistent-keychain-item-12345"
|
|
_ = deleteFromKeychain(testItemName)
|
|
|
|
// Deleting a non-existent item should NOT return an error
|
|
// This is important for cleaning up unlocker directories when the keychain item
|
|
// has already been removed (e.g., manually by user, or on a different machine)
|
|
err := deleteFromKeychain(testItemName)
|
|
assert.NoError(t, err, "Deleting non-existent keychain item should not return an error")
|
|
}
|