Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,180 @@
package passwordstore
import (
"fmt"
"sync"
"github.com/awnumar/memguard"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/native/desktop/maplefile/internal/utils"
)
// Service manages password storage in secure RAM
type Service struct {
logger *zap.Logger
mu sync.RWMutex
// RAM storage (memguard enclaves) - email -> encrypted password
memoryStore map[string]*memguard.Enclave
}
// New creates a new password storage service
func New(logger *zap.Logger) *Service {
// Initialize memguard
memguard.CatchInterrupt()
return &Service{
logger: logger,
memoryStore: make(map[string]*memguard.Enclave),
}
}
// StorePassword stores password in secure RAM (memguard).
// SECURITY NOTE: This method accepts a string for API compatibility with JSON inputs.
// The string is immediately converted to []byte and the byte slice is zeroed after
// creating the secure enclave. For maximum security, use StorePasswordBytes when
// you have direct access to []byte data.
func (s *Service) StorePassword(email, password string) error {
// Convert string to byte slice for secure handling
passwordBytes := []byte(password)
// Store using the secure byte-based method
err := s.StorePasswordBytes(email, passwordBytes)
// Zero the byte slice after use (defense in depth)
// Note: The original string cannot be zeroed in Go, but we minimize exposure
// by zeroing the byte slice copy as soon as possible
zeroBytes(passwordBytes)
return err
}
// StorePasswordBytes stores password from []byte in secure RAM (memguard).
// This is the preferred method when you have direct access to password bytes,
// as it allows the caller to zero the source bytes after this call returns.
// The provided byte slice is copied into secure memory and can be safely zeroed
// by the caller after this method returns.
func (s *Service) StorePasswordBytes(email string, password []byte) error {
s.mu.Lock()
defer s.mu.Unlock()
// Remove existing enclave if any (will be garbage collected)
if _, exists := s.memoryStore[email]; exists {
delete(s.memoryStore, email)
s.logger.Debug("Replaced existing password enclave",
zap.String("email", utils.MaskEmail(email)))
}
// Create new secure enclave (memguard copies the data into protected memory)
enclave := memguard.NewEnclave(password)
s.memoryStore[email] = enclave
s.logger.Debug("Password stored in secure RAM",
zap.String("email", utils.MaskEmail(email)))
return nil
}
// GetPassword retrieves password from secure RAM as a string.
// SECURITY NOTE: The returned string cannot be zeroed in Go. For operations
// that can work with []byte, use GetPasswordBytes instead.
func (s *Service) GetPassword(email string) (string, error) {
passwordBytes, err := s.GetPasswordBytes(email)
if err != nil {
return "", err
}
// Convert to string (unfortunately creates a copy that can't be zeroed)
password := string(passwordBytes)
// Zero the byte slice
zeroBytes(passwordBytes)
return password, nil
}
// GetPasswordBytes retrieves password from secure RAM as []byte.
// The caller SHOULD zero the returned byte slice after use by calling
// zeroBytes or similar. This is the preferred method for security-sensitive
// operations.
func (s *Service) GetPasswordBytes(email string) ([]byte, error) {
s.mu.RLock()
enclave, exists := s.memoryStore[email]
s.mu.RUnlock()
if !exists {
return nil, fmt.Errorf("no password stored for %s", email)
}
// Open enclave to read password
lockedBuffer, err := enclave.Open()
if err != nil {
return nil, fmt.Errorf("failed to open password enclave: %w", err)
}
defer lockedBuffer.Destroy()
// Copy the password bytes (memguard buffer will be destroyed after defer)
passwordBytes := make([]byte, len(lockedBuffer.Bytes()))
copy(passwordBytes, lockedBuffer.Bytes())
return passwordBytes, nil
}
// HasPassword checks if password is stored for given email
func (s *Service) HasPassword(email string) bool {
s.mu.RLock()
defer s.mu.RUnlock()
_, exists := s.memoryStore[email]
return exists
}
// ClearPassword removes password from RAM (logout)
func (s *Service) ClearPassword(email string) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, exists := s.memoryStore[email]; exists {
delete(s.memoryStore, email)
s.logger.Debug("Password cleared from RAM",
zap.String("email", utils.MaskEmail(email)))
}
return nil
}
// Cleanup destroys all secure memory (called on shutdown)
func (s *Service) Cleanup() {
s.mu.Lock()
defer s.mu.Unlock()
// Log cleanup for each stored password
for email := range s.memoryStore {
s.logger.Debug("Clearing password enclave on shutdown",
zap.String("email", utils.MaskEmail(email)))
}
// Clear the map (enclaves will be garbage collected)
s.memoryStore = make(map[string]*memguard.Enclave)
// Purge all memguard secure memory
memguard.Purge()
s.logger.Debug("Password store cleanup complete - all secure memory purged")
}
// zeroBytes overwrites a byte slice with zeros to clear sensitive data from memory.
// This is a defense-in-depth measure - while Go's GC may still have copies,
// this reduces the window of exposure.
func zeroBytes(b []byte) {
for i := range b {
b[i] = 0
}
}
// ZeroBytes is exported for callers who receive password bytes and need to clear them.
// Use this after you're done with password bytes returned by GetPasswordBytes.
func ZeroBytes(b []byte) {
zeroBytes(b)
}

View file

@ -0,0 +1,10 @@
package passwordstore
import (
"go.uber.org/zap"
)
// ProvideService creates the password storage service
func ProvideService(logger *zap.Logger) *Service {
return New(logger.Named("password-store"))
}