internal: add config loader, crypto RSA and certificate storage
This commit is contained in:
parent
fbd5e72774
commit
24c895abd7
|
|
@ -0,0 +1,112 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
basePath string
|
||||
certs map[string]*Certificate
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type Certificate struct {
|
||||
ID string
|
||||
OriginalPath 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 nil
|
||||
}
|
||||
|
||||
func (s *Storage) Store(id, origPath, password string) (string, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
exists := false
|
||||
for _, c := range s.certs {
|
||||
if c.ID == id {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if exists {
|
||||
return "", fmt.Errorf("certificate already exists with this ID")
|
||||
}
|
||||
|
||||
ext := filepath.Ext(origPath)
|
||||
storedFilename := fmt.Sprintf("%s%s", id, ext)
|
||||
storedPath := filepath.Join(s.basePath, storedFilename)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
cert := &Certificate{
|
||||
ID: id,
|
||||
OriginalPath: origPath,
|
||||
StoredPath: storedPath,
|
||||
Password: password,
|
||||
}
|
||||
s.certs[id] = cert
|
||||
|
||||
return storedPath, 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 GenerateID() string {
|
||||
hash := sha256.Sum256([]byte(fmt.Sprintf("%d", os.Getpid())))
|
||||
return hex.EncodeToString(hash[:])[:16]
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `yaml:"server"`
|
||||
VeriFactu VeriFactuConfig `yaml:"verifactu"`
|
||||
Certificates CertificateConfig `yaml:"certificates"`
|
||||
Crypto CryptoConfig `yaml:"crypto"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
}
|
||||
|
||||
type VeriFactuConfig struct {
|
||||
Environment string `yaml:"environment"`
|
||||
}
|
||||
|
||||
type CertificateConfig struct {
|
||||
StoragePath string `yaml:"storage_path"`
|
||||
}
|
||||
|
||||
type CryptoConfig struct {
|
||||
KeysPath string `yaml:"keys_path"`
|
||||
}
|
||||
|
||||
func Load(path string) (*Config, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading config file: %w", err)
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("parsing config file: %w", err)
|
||||
}
|
||||
|
||||
if cfg.Server.Port == 0 {
|
||||
cfg.Server.Port = 8080
|
||||
}
|
||||
if cfg.VeriFactu.Environment == "" {
|
||||
cfg.VeriFactu.Environment = "test"
|
||||
}
|
||||
if cfg.Certificates.StoragePath == "" {
|
||||
cfg.Certificates.StoragePath = "./certs/"
|
||||
}
|
||||
if cfg.Crypto.KeysPath == "" {
|
||||
cfg.Crypto.KeysPath = "./keys/"
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultKeyBits = 2048
|
||||
DefaultKeyDir = "./keys"
|
||||
)
|
||||
|
||||
type KeyPair struct {
|
||||
PublicKey *rsa.PublicKey
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
func GenerateKeyPair(bits int) (*KeyPair, error) {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, bits)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generating RSA key: %w", err)
|
||||
}
|
||||
return &KeyPair{
|
||||
PublicKey: &priv.PublicKey,
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *KeyPair) PublicKeyPEM() ([]byte, error) {
|
||||
pubBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling public key: %w", err)
|
||||
}
|
||||
block := &pem.Block{Type: "PUBLIC KEY", Bytes: pubBytes}
|
||||
return pem.EncodeToMemory(block), nil
|
||||
}
|
||||
|
||||
func (k *KeyPair) PrivateKeyPEM() ([]byte, error) {
|
||||
block := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k.PrivateKey)}
|
||||
return pem.EncodeToMemory(block), nil
|
||||
}
|
||||
|
||||
func LoadKeyPair(pubPath, privPath string) (*KeyPair, error) {
|
||||
pubData, err := os.ReadFile(pubPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading public key: %w", err)
|
||||
}
|
||||
|
||||
privData, err := os.ReadFile(privPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading private key: %w", err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(pubData)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("invalid public key PEM")
|
||||
}
|
||||
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing public key: %w", err)
|
||||
}
|
||||
rsaPub, ok := pub.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not an RSA public key")
|
||||
}
|
||||
|
||||
block, _ = pem.Decode(privData)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("invalid private key PEM")
|
||||
}
|
||||
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing private key: %w", err)
|
||||
}
|
||||
|
||||
return &KeyPair{
|
||||
PublicKey: rsaPub,
|
||||
PrivateKey: priv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func LoadOrCreateKeyPair(keyDir string) (*KeyPair, error) {
|
||||
if keyDir == "" {
|
||||
keyDir = DefaultKeyDir
|
||||
}
|
||||
|
||||
pubPath := filepath.Join(keyDir, "public.pem")
|
||||
privPath := filepath.Join(keyDir, "private.pem")
|
||||
|
||||
if _, err := os.Stat(pubPath); err == nil {
|
||||
if _, err := os.Stat(privPath); err == nil {
|
||||
return LoadKeyPair(pubPath, privPath)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(keyDir, 0700); err != nil {
|
||||
return nil, fmt.Errorf("creating key directory: %w", err)
|
||||
}
|
||||
|
||||
keyPair, err := GenerateKeyPair(DefaultKeyBits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubPEM, err := keyPair.PublicKeyPEM()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.WriteFile(pubPath, pubPEM, 0644); err != nil {
|
||||
return nil, fmt.Errorf("saving public key: %w", err)
|
||||
}
|
||||
|
||||
privPEM, err := keyPair.PrivateKeyPEM()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.WriteFile(privPath, privPEM, 0600); err != nil {
|
||||
return nil, fmt.Errorf("saving private key: %w", err)
|
||||
}
|
||||
|
||||
return keyPair, nil
|
||||
}
|
||||
|
||||
func Encrypt(plain []byte, pub *rsa.PublicKey) ([]byte, error) {
|
||||
return rsa.EncryptPKCS1v15(rand.Reader, pub, plain)
|
||||
}
|
||||
|
||||
func Decrypt(cipher []byte, priv *rsa.PrivateKey) ([]byte, error) {
|
||||
return rsa.DecryptPKCS1v15(rand.Reader, priv, cipher)
|
||||
}
|
||||
Loading…
Reference in New Issue