VerifactuMidAPI/internal/cert/storage.go

225 lines
4.5 KiB
Go

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"`
Password string `json:"password,omitempty"`
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())
cert := &Certificate{
ID: id,
StoredPath: storedPath,
}
s.certs[id] = cert
}
return nil
}
func (s *Storage) StoreFromBase64(id, base64Content string) (string, error) {
if err := os.MkdirAll(s.basePath, 0700); err != nil {
return "", fmt.Errorf("creating cert directory: %w", err)
}
der, err := base64.StdEncoding.DecodeString(base64Content)
if err != nil {
return "", fmt.Errorf("invalid base64: %w", err)
}
storedPath := filepath.Join(s.basePath, 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, password 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)
}
cert := &Certificate{
ID: id,
StoredPath: storedPath,
Password: password,
}
s.certs[id] = cert
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
}
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")
}