fckeuspy-go/vault_service.go

253 lines
6.6 KiB
Go
Executable File

package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
encrypt "fckeuspy-go/lib"
"fmt"
mrand "math/rand"
)
// VaultService implementuje ServiceFacade nad SecureJSONStore.
// Využívá existující hybridní šifrování z encrypt.Service (re-use kódu voláním helperů?)
// Pro jednoduchost zopakujeme potřebnou část: Encrypt/Decrypt využijí dočasný encrypt.Service
// inicializovaný z načtených klíčů.
type VaultService struct {
store encrypt.SecureJSONStore
priv *rsa.PrivateKey
pubPEM string
certPEM string
}
func NewVaultService(store encrypt.SecureJSONStore) (*VaultService, error) {
vs := &VaultService{store: store}
if err := vs.loadIdentity(); err != nil {
return nil, err
}
return vs, nil
}
func (v *VaultService) loadIdentity() error {
privPem := v.store.IdentityPrivatePEM()
if privPem == "" {
return errors.New("missing private key in store")
}
block, _ := pem.Decode([]byte(privPem))
if block == nil || block.Type != "RSA PRIVATE KEY" {
return errors.New("invalid private key PEM")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
v.priv = key
v.pubPEM = v.store.IdentityPublicPEM()
v.certPEM = v.store.IdentityCertPEM()
if v.pubPEM == "" { // derive public if missing
pubASN1, _ := x509.MarshalPKIXPublicKey(&key.PublicKey)
v.pubPEM = string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1}))
}
return nil
}
func (v *VaultService) PublicPEM() string { return v.pubPEM }
func (v *VaultService) PublicCert() string { return v.certPEM }
// Encrypt provede hybridní šifrování stejně jako původní Service.Encrypt.
func (v *VaultService) Encrypt(message, peerPEMorCert string) (string, error) {
// Vytvoříme ad-hoc Service s privátním klíčem jen pro volání existující logiky? Nejjednodušší je zkopírovat potřebnou část kódu.
return encryptHybrid(v.priv, message, peerPEMorCert)
}
// Decrypt provede rozšifrování.
func (v *VaultService) Decrypt(payload string) (string, error) { return decryptHybrid(v.priv, payload) }
// --- Lokální helpery (duplikace z encrypt.Service, zredukované) ---
type hybridEnvelope struct {
EK string `json:"ek"`
N string `json:"n"`
CT string `json:"ct"`
}
// --- Contacts management ---
type Contact struct {
ID string `json:"id"`
Name string `json:"name"`
Cert string `json:"cert"` // full PEM cert (or public key PEM)
}
const contactsKey = "contacts"
func (v *VaultService) ListContacts() ([]Contact, error) {
var list []Contact
if !v.store.Has(contactsKey) {
return []Contact{}, nil
}
if err := v.store.Get(contactsKey, &list); err != nil {
return nil, err
}
return list, nil
}
func (v *VaultService) SaveContact(c Contact) error {
list, _ := v.ListContacts()
// upsert by ID; if empty ID, assign a new unique random ID to avoid overwriting
if c.ID == "" {
c.ID = generateUniqueContactID(list)
}
replaced := false
for i := range list {
if list[i].ID == c.ID {
list[i] = c
replaced = true
break
}
}
if !replaced {
list = append(list, c)
}
if err := v.store.Put(contactsKey, list); err != nil {
return err
}
return v.store.Flush()
}
func (v *VaultService) DeleteContact(id string) error {
list, _ := v.ListContacts()
out := list[:0]
for _, c := range list {
if c.ID != id {
out = append(out, c)
}
}
if err := v.store.Put(contactsKey, out); err != nil {
return err
}
return v.store.Flush()
}
func deriveContactID(c Contact) string {
// simple stable ID: prefer CN from cert; fallback to sha256 of cert
if cn := extractCN(c.Cert); cn != "" {
return cn
}
sum := sha256.Sum256([]byte(c.Cert))
return fmt.Sprintf("%x", sum[:8])
}
// generateUniqueContactID creates a short random hex ID that does not collide with existing list.
func generateUniqueContactID(existing []Contact) string {
exists := make(map[string]struct{}, len(existing))
for _, c := range existing {
exists[c.ID] = struct{}{}
}
for {
b := make([]byte, 6)
if _, err := rand.Read(b); err != nil {
// fallback to time-based seed rand
n := mrand.Int63()
return fmt.Sprintf("%x", n)
}
id := fmt.Sprintf("%x", b)
if _, ok := exists[id]; !ok {
return id
}
}
}
func extractCN(pemText string) string {
// Support multiple concatenated PEM blocks (public key + cert, or chain)
rest := []byte(pemText)
for len(rest) > 0 {
var block *pem.Block
block, rest = pem.Decode(rest)
if block == nil {
break
}
if block.Type == "CERTIFICATE" {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
return cert.Subject.CommonName
}
}
// continue to next block until certificate found
}
return ""
}
func encryptHybrid(priv *rsa.PrivateKey, message, peerPEMorCert string) (string, error) {
pubKey, err := encrypt.ParsePeerPublicKey(peerPEMorCert)
if err != nil {
return "", err
}
aesKey := make([]byte, 32)
if _, err := rand.Read(aesKey); err != nil {
return "", err
}
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return "", err
}
ct := gcm.Seal(nil, nonce, []byte(message), nil)
ek, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, aesKey, []byte{})
if err != nil {
return "", err
}
env := hybridEnvelope{EK: base64.StdEncoding.EncodeToString(ek), N: base64.StdEncoding.EncodeToString(nonce), CT: base64.StdEncoding.EncodeToString(ct)}
out, _ := json.MarshalIndent(env, "", " ")
return string(out), nil
}
func decryptHybrid(priv *rsa.PrivateKey, payload string) (string, error) {
var env hybridEnvelope
if err := json.Unmarshal([]byte(payload), &env); err != nil {
return "", fmt.Errorf("invalid JSON: %w", err)
}
ek, err := base64.StdEncoding.DecodeString(env.EK)
if err != nil {
return "", fmt.Errorf("ek b64: %w", err)
}
nonce, err := base64.StdEncoding.DecodeString(env.N)
if err != nil {
return "", fmt.Errorf("n b64: %w", err)
}
ct, err := base64.StdEncoding.DecodeString(env.CT)
if err != nil {
return "", fmt.Errorf("ct b64: %w", err)
}
aesKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, ek, []byte{})
if err != nil {
return "", fmt.Errorf("RSA-OAEP decrypt: %w", err)
}
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
pt, err := gcm.Open(nil, nonce, ct, nil)
if err != nil {
return "", fmt.Errorf("GCM open: %w", err)
}
return string(pt), nil
}