fckeuspy-go/main.go
2025-09-08 22:16:53 +02:00

333 lines
7.9 KiB
Go

package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"html/template"
"io/fs"
"log"
"math/big"
"net/http"
"os"
"time"
)
var (
priv *rsa.PrivateKey
pubPEM []byte
certPEM []byte // self-signed cert jen pro sdílení identity (volitelné)
tmpl *template.Template
privPath = "identity_key.pem"
pubPath = "public.pem"
certPath = "identity.crt"
)
type envelope struct {
// Encrypted AES key, Nonce, Ciphertext (GCM)
EK string `json:"ek"` // base64(RSA-OAEP(aesKey))
N string `json:"n"` // base64(nonce 12B)
CT string `json:"ct"` // base64(GCM(ciphertext||tag))
}
func main() {
// 1) načti nebo vygeneruj klíče
if err := loadOrGenerateKeys(); err != nil {
log.Fatal(err)
}
// 2) šablony
tmpl = template.Must(template.ParseGlob("templates/*.html"))
// 3) routing
http.HandleFunc("/", indexHandler)
http.HandleFunc("/public.pem", publicKeyHandler)
http.HandleFunc("/public.crt", publicCertHandler)
http.HandleFunc("/encrypt", encryptHandler)
http.HandleFunc("/decrypt", decryptHandler)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
log.Println("Server běží na http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
_ = tmpl.ExecuteTemplate(w, "index.html", nil)
}
func publicKeyHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/x-pem-file")
w.Write(pubPEM)
}
func publicCertHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/x-pem-file")
w.Write(certPEM)
}
func encryptHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "Bad form", http.StatusBadRequest)
return
}
msg := r.Form.Get("message")
peer := r.Form.Get("pubkey") // může to být PEM PUBLIC KEY nebo CERT
pubKey, err := parsePeerPublicKey(peer)
if err != nil {
http.Error(w, "Neplatný public key/cert: "+err.Error(), http.StatusBadRequest)
return
}
// --- hybrid: AES-GCM + RSA-OAEP(SHA-256) ---
// 1) vygeneruj náhodný sym. klíč
aesKey := make([]byte, 32) // AES-256
if _, err := rand.Read(aesKey); err != nil {
http.Error(w, "Rand fail", 500)
return
}
block, err := aes.NewCipher(aesKey)
if err != nil {
http.Error(w, "AES fail", 500)
return
}
gcm, err := cipher.NewGCM(block)
if err != nil {
http.Error(w, "GCM fail", 500)
return
}
nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
http.Error(w, "Nonce fail", 500)
return
}
ciphertext := gcm.Seal(nil, nonce, []byte(msg), nil)
// 2) zašifruj AES klíč cizím RSA klíčem (OAEP SHA-256)
label := []byte{} // prázdný label
ek, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, aesKey, label)
if err != nil {
http.Error(w, "RSA-OAEP fail: "+err.Error(), 500)
return
}
env := envelope{
EK: base64.StdEncoding.EncodeToString(ek),
N: base64.StdEncoding.EncodeToString(nonce),
CT: base64.StdEncoding.EncodeToString(ciphertext),
}
payload, _ := json.MarshalIndent(env, "", " ")
_ = tmpl.ExecuteTemplate(w, "encrypt.html", string(payload))
}
func decryptHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "Bad form", http.StatusBadRequest)
return
}
in := r.Form.Get("payload")
var env envelope
if err := json.Unmarshal([]byte(in), &env); err != nil {
http.Error(w, "Neplatné JSON", 400)
return
}
ek, err := base64.StdEncoding.DecodeString(env.EK)
if err != nil {
http.Error(w, "ek base64", 400)
return
}
nonce, err := base64.StdEncoding.DecodeString(env.N)
if err != nil {
http.Error(w, "nonce base64", 400)
return
}
ct, err := base64.StdEncoding.DecodeString(env.CT)
if err != nil {
http.Error(w, "ct base64", 400)
return
}
// 1) rozšifruj AES klíč naším RSA
label := []byte{}
aesKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, ek, label)
if err != nil {
http.Error(w, "RSA-OAEP decrypt fail: "+err.Error(), 400)
return
}
block, err := aes.NewCipher(aesKey)
if err != nil {
http.Error(w, "AES fail", 500)
return
}
gcm, err := cipher.NewGCM(block)
if err != nil {
http.Error(w, "GCM fail", 500)
return
}
plain, err := gcm.Open(nil, nonce, ct, nil)
if err != nil {
http.Error(w, "GCM open fail: "+err.Error(), 400)
return
}
_ = tmpl.ExecuteTemplate(w, "decrypt.html", string(plain))
}
// parsePeerPublicKey umí vzít buď PEM PUBLIC KEY, nebo PEM CERT a vrátí *rsa.PublicKey
func parsePeerPublicKey(pemOrCert string) (*rsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pemOrCert))
if block == nil {
return nil, errf("nenašel jsem PEM blok")
}
switch block.Type {
case "PUBLIC KEY":
k, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
rsaPub, ok := k.(*rsa.PublicKey)
if !ok {
return nil, errf("očekávám RSA PUBLIC KEY")
}
return rsaPub, nil
case "CERTIFICATE":
c, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
rsaPub, ok := c.PublicKey.(*rsa.PublicKey)
if !ok {
return nil, errf("cert neobsahuje RSA klíč")
}
return rsaPub, nil
default:
return nil, errf("nepodporovaný PEM typ: %s", block.Type)
}
}
func generateSelfSignedCert(pub *rsa.PublicKey) []byte {
tpl := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: "Encryptor Local Identity",
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
// Tenhle cert je jen „vizitka“ (není pro TLS).
BasicConstraintsValid: true,
}
der, _ := x509.CreateCertificate(rand.Reader, &tpl, &tpl, pub, priv)
buf := &bytes.Buffer{}
_ = pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: der})
return buf.Bytes()
}
// malá helper chyba
type strErr string
func (e strErr) Error() string { return string(e) }
func errf(s string, a ...any) error { return strErr(fmtS(s, a...)) }
func fmtS(format string, a ...any) string {
var b bytes.Buffer
b.WriteString(format)
if len(a) > 0 {
b.WriteString(": ")
}
for i, v := range a {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(anyToString(v))
}
return b.String()
}
func anyToString(v any) string {
switch t := v.(type) {
case string:
return t
default:
j, _ := json.Marshal(t)
return string(j)
}
}
func loadOrGenerateKeys() error {
// existuje private key?
if fileExists(privPath) && fileExists(pubPath) && fileExists(certPath) {
// načti privátní klíč
pkBytes, err := os.ReadFile(privPath)
if err != nil {
return err
}
block, _ := pem.Decode(pkBytes)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return fmt.Errorf("invalid private key PEM")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
priv = key
// načti public & cert
pubPEM, _ = os.ReadFile(pubPath)
certPEM, _ = os.ReadFile(certPath)
log.Println("Načtena existující identita z disku.")
return nil
}
// jinak vygeneruj nové
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
priv = key
// public
pubASN1, _ := x509.MarshalPKIXPublicKey(&priv.PublicKey)
pubPEM = pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1})
// cert
certPEM = generateSelfSignedCert(&priv.PublicKey)
// ulož
if err := os.WriteFile(privPath,
pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}),
fs.FileMode(0600)); err != nil {
return err
}
if err := os.WriteFile(pubPath, pubPEM, 0644); err != nil {
return err
}
if err := os.WriteFile(certPath, certPEM, 0644); err != nil {
return err
}
log.Println("Vygenerována nová identita a uložena na disk.")
return nil
}
func fileExists(p string) bool {
info, err := os.Stat(p)
return err == nil && !info.IsDir()
}