summaryrefslogtreecommitdiffstats
path: root/nofuture.go
diff options
context:
space:
mode:
Diffstat (limited to 'nofuture.go')
-rw-r--r--nofuture.go347
1 files changed, 347 insertions, 0 deletions
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)
+ }
+}