diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 13ee2b0..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "nuxt.isNuxtApp": false -} \ No newline at end of file diff --git a/go.mod b/go.mod index 214d940..21080a2 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module fckeusp-go +module fckeuspy-go go 1.24.0 diff --git a/lib/encrypt.go b/lib/encrypt.go new file mode 100644 index 0000000..7972f12 --- /dev/null +++ b/lib/encrypt.go @@ -0,0 +1,159 @@ +package encrypt + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "fmt" + "io/fs" + "log" + "math/big" + "os" + "time" +) + +// 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, priv *rsa.PrivateKey) []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(privPath, pubPath, certPath string) (priv *rsa.PrivateKey, pubPEM, certPEM []byte, err 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 nil, nil, nil, err + } + block, _ := pem.Decode(pkBytes) + if block == nil || block.Type != "RSA PRIVATE KEY" { + return nil, nil, nil, fmt.Errorf("invalid private key PEM") + } + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, nil, nil, 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 priv, pubPEM, certPEM, nil + } + + // jinak vygeneruj nové + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, nil, err + } + priv = key + + // public + pubASN1, _ := x509.MarshalPKIXPublicKey(&priv.PublicKey) + pubPEM = pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubASN1}) + + // cert + certPEM = generateSelfSignedCert(&priv.PublicKey, priv) + + // ulož + if err := os.WriteFile(privPath, + pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}), + fs.FileMode(0600)); err != nil { + return nil, nil, nil, err + } + if err := os.WriteFile(pubPath, pubPEM, 0644); err != nil { + return nil, nil, nil, err + } + if err := os.WriteFile(certPath, certPEM, 0644); err != nil { + return nil, nil, nil, err + } + + log.Println("Vygenerována nová identita a uložena na disk.") + return priv, pubPEM, certPEM, nil +} + +func fileExists(p string) bool { + info, err := os.Stat(p) + return err == nil && !info.IsDir() +} diff --git a/main.go b/main.go index f19e711..d69b74f 100644 --- a/main.go +++ b/main.go @@ -1,25 +1,11 @@ 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" + encrypt "fckeuspy-go/lib" "html/template" - "io/fs" "log" - "math/big" "net/http" - "os" - "time" ) var ( @@ -41,292 +27,16 @@ type envelope struct { } func main() { + var err error // 1) načti nebo vygeneruj klíče - if err := loadOrGenerateKeys(); err != nil { + priv, pubPEM, certPEM, err = encrypt.LoadOrGenerateKeys(privPath, pubPath, certPath) + if 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() + muxServer := NewServer() + log.Fatal(http.ListenAndServe(":8080", muxServer)) } diff --git a/server.go b/server.go new file mode 100644 index 0000000..49c2e8f --- /dev/null +++ b/server.go @@ -0,0 +1,159 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "net/http" + + encrypt "fckeuspy-go/lib" +) + +func NewServer() *http.ServeMux { + server := &http.ServeMux{} + + // 3) routing + server.HandleFunc("/", indexHandler) + server.HandleFunc("/public.pem", publicKeyHandler) + server.HandleFunc("/public.crt", publicCertHandler) + server.HandleFunc("/encrypt", encryptHandler) + server.HandleFunc("/decrypt", decryptHandler) + server.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) + + // log.Println("Server běží na http://localhost:8080") + + return server +} + +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 := encrypt.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)) +}