Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
181
native/desktop/maplefile/internal/service/keycache/keycache.go
Normal file
181
native/desktop/maplefile/internal/service/keycache/keycache.go
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
// Package keycache provides secure in-memory caching of cryptographic keys during a session.
|
||||
// Keys are stored in memguard Enclaves (encrypted at rest in memory) and automatically
|
||||
// cleared when the application shuts down or the user logs out.
|
||||
package keycache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/awnumar/memguard"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/utils"
|
||||
)
|
||||
|
||||
// Service manages cached cryptographic keys in secure memory
|
||||
type Service struct {
|
||||
logger *zap.Logger
|
||||
mu sync.RWMutex
|
||||
// Map of email -> Enclave containing master key
|
||||
// Enclave stores data encrypted in memory, must be opened to access
|
||||
masterKeys map[string]*memguard.Enclave
|
||||
}
|
||||
|
||||
// ProvideService creates a new key cache service (for Wire)
|
||||
func ProvideService(logger *zap.Logger) *Service {
|
||||
return &Service{
|
||||
logger: logger.Named("keycache"),
|
||||
masterKeys: make(map[string]*memguard.Enclave),
|
||||
}
|
||||
}
|
||||
|
||||
// StoreMasterKey stores a user's master key in an encrypted memory Enclave
|
||||
// The key will remain cached until cleared or the app exits
|
||||
func (s *Service) StoreMasterKey(email string, masterKey []byte) error {
|
||||
if email == "" {
|
||||
return fmt.Errorf("email is required")
|
||||
}
|
||||
|
||||
if len(masterKey) == 0 {
|
||||
return fmt.Errorf("master key is empty")
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// If there's already a cached key, remove it first
|
||||
if existing, exists := s.masterKeys[email]; exists {
|
||||
// Enclaves are garbage collected when removed from map
|
||||
delete(s.masterKeys, email)
|
||||
s.logger.Debug("Replaced existing cached master key", zap.String("email", utils.MaskEmail(email)))
|
||||
_ = existing // Prevent unused variable warning
|
||||
}
|
||||
|
||||
// Create a LockedBuffer from the master key bytes first
|
||||
// This locks the memory pages and prevents swapping
|
||||
lockedBuf := memguard.NewBufferFromBytes(masterKey)
|
||||
|
||||
// Create an Enclave from the LockedBuffer
|
||||
// Enclave stores the data encrypted at rest in memory
|
||||
enclave := lockedBuf.Seal()
|
||||
|
||||
// The LockedBuffer is consumed by Seal(), so we don't need to Destroy() it
|
||||
|
||||
// Store the enclave
|
||||
s.masterKeys[email] = enclave
|
||||
|
||||
s.logger.Info("Master key cached securely in memory",
|
||||
zap.String("email", utils.MaskEmail(email)),
|
||||
zap.Int("size", len(masterKey)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMasterKey retrieves a cached master key for the given email
|
||||
// Returns the key bytes and a cleanup function that MUST be called when done
|
||||
// The cleanup function destroys the LockedBuffer to prevent memory leaks
|
||||
func (s *Service) GetMasterKey(email string) ([]byte, func(), error) {
|
||||
if email == "" {
|
||||
return nil, nil, fmt.Errorf("email is required")
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
enclave, exists := s.masterKeys[email]
|
||||
s.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return nil, nil, fmt.Errorf("no cached master key found for email: %s", email)
|
||||
}
|
||||
|
||||
// Open the enclave to access the master key
|
||||
lockedBuf, err := enclave.Open()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to open enclave for reading: %w", err)
|
||||
}
|
||||
|
||||
// Get the bytes (caller will use these)
|
||||
masterKey := lockedBuf.Bytes()
|
||||
|
||||
// Return cleanup function that destroys the LockedBuffer
|
||||
cleanup := func() {
|
||||
lockedBuf.Destroy()
|
||||
}
|
||||
|
||||
s.logger.Debug("Retrieved cached master key from secure memory",
|
||||
zap.String("email", utils.MaskEmail(email)))
|
||||
|
||||
return masterKey, cleanup, nil
|
||||
}
|
||||
|
||||
// WithMasterKey provides a callback pattern for using a cached master key
|
||||
// The key is automatically cleaned up after the callback returns
|
||||
func (s *Service) WithMasterKey(email string, fn func([]byte) error) error {
|
||||
masterKey, cleanup, err := s.GetMasterKey(email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
return fn(masterKey)
|
||||
}
|
||||
|
||||
// HasMasterKey checks if a master key is cached for the given email
|
||||
func (s *Service) HasMasterKey(email string) bool {
|
||||
if email == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
_, exists := s.masterKeys[email]
|
||||
return exists
|
||||
}
|
||||
|
||||
// ClearMasterKey removes a cached master key for a specific user
|
||||
func (s *Service) ClearMasterKey(email string) error {
|
||||
if email == "" {
|
||||
return fmt.Errorf("email is required")
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if enclave, exists := s.masterKeys[email]; exists {
|
||||
delete(s.masterKeys, email)
|
||||
s.logger.Info("Cleared cached master key from secure memory",
|
||||
zap.String("email", utils.MaskEmail(email)))
|
||||
_ = enclave // Enclave will be garbage collected
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("no cached master key found for email: %s", email)
|
||||
}
|
||||
|
||||
// ClearAll removes all cached master keys
|
||||
// This should be called on logout or application shutdown
|
||||
func (s *Service) ClearAll() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
count := len(s.masterKeys)
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Clear all enclaves
|
||||
for email := range s.masterKeys {
|
||||
delete(s.masterKeys, email)
|
||||
}
|
||||
|
||||
s.logger.Info("Cleared all cached master keys from secure memory",
|
||||
zap.Int("count", count))
|
||||
}
|
||||
|
||||
// Cleanup performs cleanup operations when the service is shutting down
|
||||
// This is called by the application shutdown handler
|
||||
func (s *Service) Cleanup() {
|
||||
s.logger.Info("Cleaning up key cache service")
|
||||
s.ClearAll()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue