package cert import ( "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "fmt" "os" "path/filepath" "strings" "sync" ) type Storage struct { basePath string certs map[string]*Certificate mu sync.RWMutex } type Certificate struct { ID string `json:"id"` StoredPath string `json:"stored_path"` Token string `json:"token,omitempty"` } type TokenData struct { Token string CertID string StoredPath string Password string } func NewStorage(basePath string) *Storage { if basePath == "" { basePath = "./certs/" } return &Storage{ basePath: basePath, certs: make(map[string]*Certificate), } } func (s *Storage) Init() error { if err := os.MkdirAll(s.basePath, 0700); err != nil { return fmt.Errorf("creating cert storage directory: %w", err) } return s.loadFromDisk() } func (s *Storage) loadFromDisk() error { entries, err := os.ReadDir(s.basePath) if err != nil { return nil } for _, entry := range entries { if entry.IsDir() { continue } ext := filepath.Ext(entry.Name()) if ext != ".p12" && ext != ".pfx" { continue } id := entry.Name()[:len(entry.Name())-len(ext)] storedPath := filepath.Join(s.basePath, entry.Name()) s.certs[id] = &Certificate{ ID: id, StoredPath: storedPath, } } return nil } func (s *Storage) StoreFromBase64(id, base64Content string) (string, error) { tmpDir := filepath.Join(s.basePath, "tmp") if err := os.MkdirAll(tmpDir, 0700); err != nil { return "", fmt.Errorf("creating tmp directory: %w", err) } der, err := base64.StdEncoding.DecodeString(base64Content) if err != nil { return "", fmt.Errorf("invalid base64: %w", err) } storedPath := filepath.Join(tmpDir, id+".p12") if err := os.WriteFile(storedPath, der, 0600); err != nil { return "", fmt.Errorf("writing certificate: %w", err) } return storedPath, nil } func (s *Storage) MoveToPerm(id, tempPath string) (string, error) { s.mu.Lock() defer s.mu.Unlock() storedPath := filepath.Join(s.basePath, id+".p12") if _, err := os.Stat(storedPath); err == nil { if err := os.Remove(storedPath); err != nil { return "", fmt.Errorf("removing existing certificate: %w", err) } } if err := os.Rename(tempPath, storedPath); err != nil { return "", fmt.Errorf("moving certificate: %w", err) } s.certs[id] = &Certificate{ ID: id, StoredPath: storedPath, } return storedPath, nil } func (s *Storage) DeleteTemp(tempPath string) error { if err := os.Remove(tempPath); err != nil { return fmt.Errorf("deleting temp certificate: %w", err) } return nil } func (s *Storage) Get(id string) (*Certificate, error) { s.mu.RLock() defer s.mu.RUnlock() cert, ok := s.certs[id] if !ok { return nil, fmt.Errorf("certificate not found") } return cert, nil } func (s *Storage) Delete(id string) error { s.mu.Lock() defer s.mu.Unlock() cert, ok := s.certs[id] if !ok { return fmt.Errorf("certificate not found") } if err := os.Remove(cert.StoredPath); err != nil { return fmt.Errorf("removing certificate file: %w", err) } delete(s.certs, id) return nil } func (s *Storage) List() []*Certificate { s.mu.RLock() defer s.mu.RUnlock() certs := make([]*Certificate, 0, len(s.certs)) for _, cert := range s.certs { certs = append(certs, cert) } return certs } func (s *Storage) Hash(data []byte) string { hash := sha256.Sum256(data) return hex.EncodeToString(hash[:]) } func (s *Storage) generateToken() (string, error) { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return "", fmt.Errorf("generating token: %w", err) } return strings.ToUpper(hex.EncodeToString(b)), nil } func (s *Storage) GenerateToken(id, storedPath, password string) (*TokenData, error) { s.mu.Lock() defer s.mu.Unlock() token, err := s.generateToken() if err != nil { return nil, err } cert, ok := s.certs[id] if !ok { return nil, fmt.Errorf("certificate not found") } cert.Token = token return &TokenData{ Token: token, CertID: id, StoredPath: storedPath, Password: password, }, nil } func (s *Storage) GetByToken(token string) (*TokenData, error) { s.mu.RLock() defer s.mu.RUnlock() for _, cert := range s.certs { if cert.Token == token { return &TokenData{ Token: cert.Token, CertID: cert.ID, StoredPath: cert.StoredPath, }, nil } } return nil, fmt.Errorf("token not found") }