From b5ff9cd0a133c346dff9a9497022c3f3848c0e66 Mon Sep 17 00:00:00 2001 From: Gab <24553253+gabrix73@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:00:59 +0100 Subject: Add files via upload This secure messaging application enables encrypted communication through mainstream messaging platforms without requiring extensions. The session-based encryption ensures your messages remain secure while active, and once ended, the encryption keys are securely wiped from memory using Memguard protection. --- nofuture.go | 347 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 nofuture.go (limited to 'nofuture.go') diff --git a/nofuture.go b/nofuture.go new file mode 100644 index 0000000..44c02d9 --- /dev/null +++ b/nofuture.go @@ -0,0 +1,347 @@ +package main + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "log" + "net/http" + "runtime" + "strings" + "sync" + + "github.com/awnumar/memguard" + "github.com/gin-gonic/gin" + "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/nacl/box" +) + +type Session struct { + PrivateKey *memguard.LockedBuffer + PublicKey *memguard.LockedBuffer + BuddyPublicKey *memguard.LockedBuffer +} + +var ( + sessions sync.Map +) + +const ( + nonceLength = 24 + keyLength = 32 +) + +func init() { + memguard.CatchInterrupt() + runtime.GC() +} + +func generateKeyPair() (*memguard.LockedBuffer, *memguard.LockedBuffer, error) { + privateKeyBuf := memguard.NewBuffer(keyLength) + defer privateKeyBuf.Destroy() + + if _, err := rand.Read(privateKeyBuf.Bytes()); err != nil { + return nil, nil, fmt.Errorf("key generation failed: %w", err) + } + + publicKeyBuf := memguard.NewBuffer(keyLength) + defer publicKeyBuf.Destroy() + + publicKey, err := curve25519.X25519(privateKeyBuf.Bytes(), curve25519.Basepoint) + if err != nil { + return nil, nil, fmt.Errorf("public key derivation failed: %w", err) + } + + privateKeyLocked := memguard.NewBuffer(keyLength) + publicKeyLocked := memguard.NewBuffer(keyLength) + + copy(privateKeyLocked.Bytes(), privateKeyBuf.Bytes()) + copy(publicKeyLocked.Bytes(), publicKey) + + return privateKeyLocked, publicKeyLocked, nil +} + +func startSession(c *gin.Context) { + c.Header("Content-Type", "application/json") + log.Printf("Starting new secure session...") + + privateKey, publicKey, err := generateKeyPair() + if err != nil { + log.Printf("Error generating key pair: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + sessionIDBuf := memguard.NewBuffer(12) + if _, err := rand.Read(sessionIDBuf.Bytes()); err != nil { + log.Printf("Error generating session ID: %v", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate session ID"}) + return + } + defer sessionIDBuf.Destroy() + + sessionID := base64.RawURLEncoding.EncodeToString(sessionIDBuf.Bytes()) + + sessions.Store(sessionID, &Session{ + PrivateKey: privateKey, + PublicKey: publicKey, + BuddyPublicKey: nil, + }) + + log.Printf("Session created successfully: %s", sessionID) + + c.JSON(http.StatusOK, gin.H{ + "session_id": sessionID, + "public_key": base64.StdEncoding.EncodeToString(publicKey.Bytes()), + }) + + runtime.GC() +} + +func endSession(c *gin.Context) { + c.Header("Content-Type", "application/json") + var req struct { + SessionID string `json:"session_id" binding:"required"` + } + + if err := c.BindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + return + } + + if sess, loaded := sessions.LoadAndDelete(req.SessionID); loaded { + session := sess.(*Session) + if session.PrivateKey != nil { + session.PrivateKey.Destroy() + } + if session.PublicKey != nil { + session.PublicKey.Destroy() + } + if session.BuddyPublicKey != nil { + session.BuddyPublicKey.Destroy() + } + } + + log.Printf("Session ended and cleaned up: %s", req.SessionID) + c.JSON(http.StatusOK, gin.H{"status": "session_ended"}) + + runtime.GC() +} + +func pairSessions(c *gin.Context) { + c.Header("Content-Type", "application/json") + var req struct { + SessionIDA string `json:"session_id_A" binding:"required"` + SessionIDB string `json:"session_id_B" binding:"required"` + } + + if err := c.BindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + return + } + + sessionA, ok := sessions.Load(req.SessionIDA) + if !ok { + c.JSON(http.StatusNotFound, gin.H{"error": "session A not found"}) + return + } + + sessionB, ok := sessions.Load(req.SessionIDB) + if !ok { + c.JSON(http.StatusNotFound, gin.H{"error": "session B not found"}) + return + } + + sessA := sessionA.(*Session) + sessB := sessionB.(*Session) + + sessA.BuddyPublicKey = memguard.NewBuffer(keyLength) + sessB.BuddyPublicKey = memguard.NewBuffer(keyLength) + + copy(sessA.BuddyPublicKey.Bytes(), sessB.PublicKey.Bytes()) + copy(sessB.BuddyPublicKey.Bytes(), sessA.PublicKey.Bytes()) + + log.Printf("Sessions paired: %s with %s", req.SessionIDA, req.SessionIDB) + c.JSON(http.StatusOK, gin.H{ + "status": "paired", + "session_id_A": req.SessionIDA, + "session_id_B": req.SessionIDB, + }) + + runtime.GC() +} + +func buddyEncrypt(c *gin.Context) { + c.Header("Content-Type", "application/json") + var req struct { + SessionID string `json:"session_id" binding:"required"` + Plaintext string `json:"plaintext" binding:"required"` + } + + if err := c.BindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + return + } + + session, ok := sessions.Load(req.SessionID) + if !ok { + c.JSON(http.StatusNotFound, gin.H{"error": "session not found"}) + return + } + + sess := session.(*Session) + if sess.BuddyPublicKey == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "session not paired"}) + return + } + + privateKeyArray := memguard.NewBuffer(keyLength) + buddyPubArray := memguard.NewBuffer(keyLength) + defer privateKeyArray.Destroy() + defer buddyPubArray.Destroy() + + copy(privateKeyArray.Bytes(), sess.PrivateKey.Bytes()) + copy(buddyPubArray.Bytes(), sess.BuddyPublicKey.Bytes()) + + nonceBuf := memguard.NewBuffer(nonceLength) + defer nonceBuf.Destroy() + + if _, err := rand.Read(nonceBuf.Bytes()); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "nonce generation failed"}) + return + } + + var nonce [nonceLength]byte + copy(nonce[:], nonceBuf.Bytes()) + + var privateKeyArr, buddyPubArr [keyLength]byte + copy(privateKeyArr[:], privateKeyArray.Bytes()) + copy(buddyPubArr[:], buddyPubArray.Bytes()) + + plaintextBuf := memguard.NewBufferFromBytes([]byte(req.Plaintext)) + defer plaintextBuf.Destroy() + + encrypted := box.Seal(nonce[:], plaintextBuf.Bytes(), &nonce, &buddyPubArr, &privateKeyArr) + + c.JSON(http.StatusOK, gin.H{ + "encrypted_b64": base64.StdEncoding.EncodeToString(encrypted), + }) + + runtime.GC() +} + +func buddyDecrypt(c *gin.Context) { + c.Header("Content-Type", "application/json") + var req struct { + SessionID string `json:"session_id" binding:"required"` + EncryptedB64 string `json:"encrypted_b64" binding:"required"` + } + + if err := c.BindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + return + } + + session, ok := sessions.Load(req.SessionID) + if !ok { + c.JSON(http.StatusNotFound, gin.H{"error": "session not found"}) + return + } + + sess := session.(*Session) + if sess.BuddyPublicKey == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "session not paired"}) + return + } + + encrypted, err := base64.StdEncoding.DecodeString(req.EncryptedB64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid base64 encoding"}) + return + } + + if len(encrypted) < nonceLength { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ciphertext length"}) + return + } + + privateKeyArray := memguard.NewBuffer(keyLength) + buddyPubArray := memguard.NewBuffer(keyLength) + defer privateKeyArray.Destroy() + defer buddyPubArray.Destroy() + + copy(privateKeyArray.Bytes(), sess.PrivateKey.Bytes()) + copy(buddyPubArray.Bytes(), sess.BuddyPublicKey.Bytes()) + + var privateKeyArr, buddyPubArr [keyLength]byte + var nonce [nonceLength]byte + copy(privateKeyArr[:], privateKeyArray.Bytes()) + copy(buddyPubArr[:], buddyPubArray.Bytes()) + copy(nonce[:], encrypted[:nonceLength]) + + decrypted, ok := box.Open(nil, encrypted[nonceLength:], &nonce, &buddyPubArr, &privateKeyArr) + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": "decryption failed"}) + return + } + + c.JSON(http.StatusOK, gin.H{"plaintext": string(decrypted)}) + runtime.GC() +} + +func randomBytes(n int) []byte { + b := make([]byte, n) + if _, err := rand.Read(b); err != nil { + panic(err) + } + return b +} + +func main() { + memguard.CatchInterrupt() + defer memguard.Purge() + + log.Printf("Starting Nofuture server with enhanced memory protection...") + + gin.SetMode(gin.ReleaseMode) + r := gin.New() + r.Use(gin.Recovery()) + + // CORS middleware only for API routes + r.Use(func(c *gin.Context) { + if strings.HasPrefix(c.Request.URL.Path, "/api/") { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "POST, OPTIONS") + c.Header("Access-Control-Allow-Headers", "Content-Type") + c.Header("Content-Type", "application/json") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + } + c.Next() + }) + + // Create API group with its own middleware + api := r.Group("/api") + { + api.POST("/start_session", startSession) + api.POST("/end_session", endSession) + api.POST("/pair_sessions", pairSessions) + api.POST("/buddy_encrypt", buddyEncrypt) + api.POST("/buddy_decrypt", buddyDecrypt) + } + + // Serve static files + r.StaticFile("/", "./index.html") + r.Static("/js", "./js") + r.Static("/static", "./static") + + serverAddr := "127.0.0.1:3000" + log.Printf("Server starting on %s", serverAddr) + + if err := r.Run(serverAddr); err != nil { + log.Fatalf("Server startup failed: %v", err) + } +} -- cgit v1.2.3