2026-04-08 12:31:09 +00:00
|
|
|
package cert
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-17 11:03:06 +00:00
|
|
|
"crypto/rand"
|
2026-04-08 12:31:09 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2026-04-17 11:03:06 +00:00
|
|
|
"strings"
|
2026-04-08 12:31:09 +00:00
|
|
|
"sync"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Storage struct {
|
|
|
|
|
basePath string
|
|
|
|
|
certs map[string]*Certificate
|
|
|
|
|
mu sync.RWMutex
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Certificate struct {
|
2026-04-17 11:03:06 +00:00
|
|
|
ID string `json:"id"`
|
|
|
|
|
OriginalPath string `json:"original_path"`
|
|
|
|
|
StoredPath string `json:"stored_path"`
|
|
|
|
|
Password string `json:"password,omitempty"`
|
|
|
|
|
Token string `json:"token,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TokenData struct {
|
|
|
|
|
Token string
|
|
|
|
|
CertID string
|
|
|
|
|
StoredPath string
|
|
|
|
|
Password string
|
2026-04-08 12:31:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
2026-04-17 11:03:06 +00:00
|
|
|
|
|
|
|
|
// Load existing certificates from disk
|
|
|
|
|
return s.loadFromDisk()
|
2026-04-08 12:31:09 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:03:06 +00:00
|
|
|
func (s *Storage) loadFromDisk() error {
|
|
|
|
|
entries, err := os.ReadDir(s.basePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-04-08 12:31:09 +00:00
|
|
|
|
2026-04-17 11:03:06 +00:00
|
|
|
for _, entry := range entries {
|
|
|
|
|
if entry.IsDir() {
|
|
|
|
|
continue
|
2026-04-08 12:31:09 +00:00
|
|
|
}
|
2026-04-17 11:03:06 +00:00
|
|
|
|
|
|
|
|
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())
|
|
|
|
|
|
|
|
|
|
cert := &Certificate{
|
|
|
|
|
ID: id,
|
|
|
|
|
StoredPath: storedPath,
|
|
|
|
|
}
|
|
|
|
|
s.certs[id] = cert
|
2026-04-08 12:31:09 +00:00
|
|
|
}
|
2026-04-17 11:03:06 +00:00
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Storage) StoreTemp(id, origPath, password string) (string, error) {
|
|
|
|
|
tmpPath := filepath.Join(s.basePath, "tmp")
|
|
|
|
|
if err := os.MkdirAll(tmpPath, 0700); err != nil {
|
|
|
|
|
return "", fmt.Errorf("creating tmp directory: %w", err)
|
2026-04-08 12:31:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ext := filepath.Ext(origPath)
|
|
|
|
|
storedFilename := fmt.Sprintf("%s%s", id, ext)
|
2026-04-17 11:03:06 +00:00
|
|
|
storedPath := filepath.Join(tmpPath, storedFilename)
|
2026-04-08 12:31:09 +00:00
|
|
|
|
|
|
|
|
data, err := os.ReadFile(origPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("reading certificate file: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := os.WriteFile(storedPath, data, 0600); err != nil {
|
|
|
|
|
return "", fmt.Errorf("storing certificate: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:03:06 +00:00
|
|
|
return storedPath, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Storage) MoveToPerm(id, tempPath, password string) (string, error) {
|
|
|
|
|
s.mu.Lock()
|
|
|
|
|
defer s.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
ext := filepath.Ext(tempPath)
|
|
|
|
|
storedFilename := fmt.Sprintf("%s%s", id, ext)
|
|
|
|
|
storedPath := filepath.Join(s.basePath, storedFilename)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 12:31:09 +00:00
|
|
|
cert := &Certificate{
|
2026-04-17 11:03:06 +00:00
|
|
|
ID: id,
|
|
|
|
|
StoredPath: storedPath,
|
|
|
|
|
Password: password,
|
2026-04-08 12:31:09 +00:00
|
|
|
}
|
|
|
|
|
s.certs[id] = cert
|
|
|
|
|
|
|
|
|
|
return storedPath, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:03:06 +00:00
|
|
|
func (s *Storage) DeleteTemp(tempPath string) error {
|
|
|
|
|
if err := os.Remove(tempPath); err != nil {
|
|
|
|
|
return fmt.Errorf("deleting temp certificate: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 12:31:09 +00:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:03:06 +00:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tokenData := &TokenData{
|
|
|
|
|
Token: token,
|
|
|
|
|
CertID: id,
|
|
|
|
|
StoredPath: storedPath,
|
|
|
|
|
Password: password,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cert, ok := s.certs[id]
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, fmt.Errorf("certificate not found")
|
|
|
|
|
}
|
|
|
|
|
cert.Token = token
|
|
|
|
|
|
|
|
|
|
return tokenData, 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,
|
|
|
|
|
Password: cert.Password,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("token not found")
|
2026-04-08 12:31:09 +00:00
|
|
|
}
|