/
routes.go
executable file
·148 lines (129 loc) · 3.22 KB
/
routes.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package selfdestruct
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"net/http"
"strings"
"time"
"golang.org/x/crypto/nacl/secretbox"
"github.com/nu7hatch/gouuid"
"google.golang.org/appengine"
"google.golang.org/appengine/memcache"
)
func init() {
http.HandleFunc("/", handleIndex)
http.HandleFunc("/msg/", handleMessage)
}
func encrypt(decrypted string, password [32]byte) string {
var nonce [24]byte
io.ReadAtLeast(rand.Reader, nonce[:], 24)
encrypted := secretbox.Seal(nil, []byte(decrypted), &nonce, &password)
return fmt.Sprintf("%x:%x", nonce[:], encrypted)
}
func decrypt(encrypted string, password [32]byte) (string, error) {
var nonce [24]byte
parts := strings.SplitN(encrypted, ":", 2)
if len(parts) < 2 {
return "", fmt.Errorf("expected nonce")
}
bs, err := hex.DecodeString(parts[0])
if err != nil || len(bs) != 24 {
return "", fmt.Errorf("invalid nonce")
}
copy(nonce[:], bs)
bs, err = hex.DecodeString(parts[1])
if err != nil {
return "", fmt.Errorf("invalid message")
}
decrypted, ok := secretbox.Open(nil, bs, &nonce, &password)
if !ok {
return "", fmt.Errorf("invalid message")
}
return string(decrypted), nil
}
func generatePassword() [32]byte {
var password [32]byte
io.ReadAtLeast(rand.Reader, password[:], 32)
return password
}
// create a message
func handleIndex(res http.ResponseWriter, req *http.Request) {
ctx := appengine.NewContext(req)
// form submit
if req.Method == "POST" {
msg := req.FormValue("message")
secretKey := generatePassword()
encryptedMessage := encrypt(msg, secretKey)
messageKey, _ := uuid.NewV4()
// store the message in memcache
item := &memcache.Item{
Key: messageKey.String(),
Value: []byte(encryptedMessage),
}
err := memcache.Add(ctx, item)
if err != nil {
http.Error(res, err.Error(), 500)
return
}
io.WriteString(res, `<!DOCTYPE html>
<html>
<head>
</head>
<body>
Here is your self-destructing secret message ID:
<a href="/msg/`+messageKey.String()+`?secret=`+fmt.Sprintf("%x", secretKey)+`">`+messageKey.String()+`</a>
Send it to Peter Graves.
<p>The encrypted message:</p>
<p>`+encryptedMessage+`</p>
</body>
</html>`)
} else {
// render the form
io.WriteString(res, `<!DOCTYPE html>
<html>
<head>
</head>
<body>
<form method="POST">
<label>Message:
<textarea name="message"></textarea>
</label><br>
<input type="submit">
</form>
</body>
</html>`)
}
}
// return a message based on its id
func handleMessage(res http.ResponseWriter, req *http.Request) {
ctx := appengine.NewContext(req)
// get key from URL
key := strings.SplitN(req.URL.Path, "/", 3)[2]
// get item from memcache
item, err := memcache.Get(ctx, key)
if err != nil {
http.NotFound(res, req)
return
}
var secretKey [32]byte
bs, err := hex.DecodeString(req.FormValue("secret"))
if err != nil || len(bs) != 32 {
http.NotFound(res, req)
return
}
copy(secretKey[:], bs)
msg, err := decrypt(string(item.Value), secretKey)
if err != nil {
http.NotFound(res, req)
return
}
// if this is the first time the message is viewed
if item.Flags == 0 {
item.Expiration = 30 * time.Second
item.Flags = 1
memcache.Set(ctx, item)
}
res.Write([]byte(msg))
}