-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
147 lines (134 loc) · 3.7 KB
/
main.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
package main
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"flag"
"fmt"
"io"
"log"
"net/http"
"strconv"
"time"
"unicode/utf8"
"golang.org/x/time/rate"
"gopkg.in/yaml.v2"
)
// GET requests to '/' or '/verify' are redirected to the github repo.
const repo = "https://github.com/jdtw/commit"
var (
port = flag.Int("port", 8080, "listening port")
ratelimit = flag.Duration("rate", 200*time.Millisecond, "global rate limit")
)
func main() {
flag.Parse()
addr := fmt.Sprint(":", *port)
log.Printf("listening on %s", addr)
srv := http.NewServeMux()
srv.HandleFunc("/verify", postHandler(verify()))
srv.HandleFunc("/", postHandler(commit()))
log.Fatal(http.ListenAndServe(addr, srv))
}
type commitment struct {
Message string `yaml:"message"`
Key string `yaml:"key"`
Commit string `yaml:"commit"`
}
// commit returns a handler that reads the message from the request
// body, appends some entropy, and creates a commitment by taking the
// SHA256 hash of the message+entropy. It returns the message and
// digest as YAML.
func commit() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
internalError(w, "failed to read body: %v", err)
return
}
if len(body) == 0 {
badRequest(w, "")
return
}
if !utf8.Valid(body) {
badRequest(w, "body must be valid utf8!")
}
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
internalError(w, "failed to read entropy: %v", err)
return
}
h := hmac.New(sha256.New, key)
h.Write(body)
digest := h.Sum(nil)
w.Header().Set("Content-Type", "text/yaml")
yaml.NewEncoder(w).Encode(&commitment{
Key: hex.EncodeToString(key),
Message: string(body),
Commit: hex.EncodeToString(digest),
})
}
}
// verify returns a handler that decodes the commitment YAML in the
// body and verifies that the digest of the message matches the
// commit. If it does, it returns "true" in the body, otherwise
// "false".
func verify() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
c := commitment{}
if err := yaml.NewDecoder(r.Body).Decode(&c); err != nil {
badRequest(w, "failed to decody body: %v", err)
return
}
got, err := hex.DecodeString(c.Commit)
if err != nil {
badRequest(w, "bad commitment: %v", err)
return
}
key, err := hex.DecodeString(c.Key)
if err != nil {
badRequest(w, "bad key: %v", err)
return
}
h := hmac.New(sha256.New, key)
h.Write([]byte(c.Message))
want := h.Sum(nil)
verified := hmac.Equal(got, want)
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, strconv.FormatBool(verified))
}
}
// postHandler is middleware that redirects GET requests to the github
// repo, returns method not allowed for anything other than POST, and
// does rate limiting.
func postHandler(h http.HandlerFunc) http.HandlerFunc {
limiter := rate.NewLimiter(rate.Every(*ratelimit), 5)
return func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
http.Redirect(w, r, repo, http.StatusFound)
return
}
if r.Method != "POST" {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
if !limiter.Allow() {
http.Error(w, "Are you like, trying to mine bitcoin or something?", http.StatusTooManyRequests)
return
}
h(w, r)
}
}
func internalError(w http.ResponseWriter, format string, a ...interface{}) {
msg := fmt.Sprintf(format, a...)
log.Print(msg)
http.Error(w, msg, http.StatusInternalServerError)
}
func badRequest(w http.ResponseWriter, format string, a ...interface{}) {
msg := fmt.Sprintf(format, a...)
http.Error(w, msg, http.StatusBadRequest)
}