lifecycle

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 22, 2026 License: Apache-2.0 Imports: 2 Imported by: 0

Documentation

Overview

Package lifecycle provides generic wrappers for managing sensitive data lifecycle. The primary concern is ensuring cryptographic material is properly zeroized when no longer needed, following modern Go patterns with generics.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func WithSecureValue

func WithSecureValue[T any](value T, zeroizer Zeroizer[T], userFunc func(value *T) error) error

WithSecureValue provides a "loan pattern" for secure lifecycle management. It handles the entire New()->Use()->Destroy() lifecycle automatically, eliminating the possibility of forgetting to call Destroy().

This is the recommended API for most use cases. It guarantees:

  1. The value is always zeroized, even if userFunc panics
  2. No possibility of use-after-destroy bugs
  3. Clean, self-contained code blocks

Example usage:

// Sign a message with ephemeral Ed25519 key
err := lifecycle.WithSecureValue(privateKey, zeroizer, func(key *ed25519.PrivateKey) error {
    signature := ed25519.Sign(*key, message)
    return sendSignature(signature)
})

// The key is automatically zeroized here, even if sendSignature panicked

For long-lived objects, use New()/Destroy() directly instead.

func WithSecureValueResult

func WithSecureValueResult[T any, R any](value T, zeroizer Zeroizer[T], userFunc func(value *T) (R, error)) (R, error)

WithSecureValueResult provides the loan pattern with a return value. This is useful when you need to extract a result from the secure operation without leaking the sensitive value.

Example usage:

signature, err := lifecycle.WithSecureValueResult(privateKey, zeroizer,
    func(key *ed25519.PrivateKey) ([]byte, error) {
        sig := ed25519.Sign(*key, message)
        return sig, nil
    },
)

Types

type SecureValue

type SecureValue[T any] struct {
	// contains filtered or unexported fields
}

SecureValue wraps a sensitive value with lifecycle management. It ensures that:

  1. Access to the value is controlled via Use() method
  2. The value is automatically zeroized when Destroy() is called
  3. Operations are concurrency-safe
  4. Callbacks cannot leak copies of sensitive data

Type parameter T can be any type that holds sensitive data (keys, secrets, etc.)

Example usage:

// Create a zeroizer for ed25519 private keys
zeroizer := func(key *ed25519.PrivateKey) {
	for i := range *key {
		(*key)[i] = 0
	}
}

// Wrap the key
secureKey := lifecycle.New(privateKey, zeroizer)
defer secureKey.Destroy()

// Use the key safely (receives pointer to prevent copying)
err := secureKey.Use(func(key *ed25519.PrivateKey) error {
	signature := ed25519.Sign(*key, message)
	return nil
})

func New

func New[T any](value T, zeroizer Zeroizer[T]) *SecureValue[T]

New creates a new SecureValue wrapper around a sensitive value. The zeroizer function must properly zeroize the value's memory.

func (*SecureValue[T]) Destroy

func (s *SecureValue[T]) Destroy()

Destroy securely zeros the wrapped value and marks it as destroyed. After calling Destroy, all subsequent Use() calls will fail.

Destroy is idempotent - calling it multiple times is safe. Destroy will block until all active Use() operations complete, ensuring safe zeroization without race conditions.

Example usage:

secureKey := lifecycle.New(privateKey, zeroizer)
defer secureKey.Destroy()  // Safe to call even with concurrent Use()

go secureKey.Use(...)  // These can run concurrently
go secureKey.Use(...)
// Destroy() will wait for all Use() calls to complete

func (*SecureValue[T]) IsDestroyed

func (s *SecureValue[T]) IsDestroyed() bool

IsDestroyed returns true if Destroy() has been called. This is primarily useful for testing and debugging.

func (*SecureValue[T]) Use

func (s *SecureValue[T]) Use(f func(value *T) error) (err error)

Use provides temporary, safe access to the wrapped value. The callback function f receives a POINTER to the value to prevent copying sensitive data. This ensures that only the internal copy can be zeroized.

Use() is safe for concurrent calls from multiple goroutines. Destroy() will block until all active Use() calls complete.

LOCK ORDERING GUARANTEE: The lock ordering (RWMutex → WaitGroup) is safe because:

  1. Use() acquires RLock, checks destroyed flag, calls inUse.Add(1), then releases RLock
  2. Destroy() acquires exclusive Lock (blocks until all RLocks released)
  3. Destroy() sets destroyed=true, releases Lock, then calls inUse.Wait()
  4. By the time Wait() is called, all RLocks are released and no new Add() can occur

This prevents the race where Wait() returns before Add() is called, because Destroy's exclusive Lock ensures all Use() calls have either:

  • Completed their Add() before Lock was acquired, OR
  • Will see destroyed=true and fail before calling Add()

PANIC SAFETY: If the callback panics, the value is immediately zeroized before re-panicking. This prevents sensitive data from lingering in memory after a crash. For callers using defer Destroy(), the panic recovery provides defense-in-depth: even if Destroy() is somehow skipped, the panic path ensures zeroization.

SECURITY: The callback receives a pointer to prevent accidental copies. Callers should NOT store the pointer or create copies of the value.

type Zeroizer

type Zeroizer[T any] func(value *T)

Zeroizer is a function that knows how to securely zero a value of type T. Implementations should overwrite the value's memory with zeros to prevent sensitive data from lingering in memory.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL