package main import ( "crypto/rand" "encoding/base64" "html/template" "io" "net/http" ) // start: vendored from nosurf const tokenLength = 32 func generateToken() []byte { bytes := make([]byte, tokenLength) if _, err := io.ReadFull(rand.Reader, bytes); err != nil { panic(err) } return bytes } func b64encode(data []byte) string { return base64.StdEncoding.EncodeToString(data) } func b64decode(data string) []byte { decoded, err := base64.StdEncoding.DecodeString(data) if err != nil { return nil } return decoded } // Masks/unmasks the given data *in place* // with the given key // Slices must be of the same length, or oneTimePad will panic func oneTimePad(data, key []byte) { n := len(data) if n != len(key) { panic("Lengths of slices are not equal") } for i := 0; i < n; i++ { data[i] ^= key[i] } } func maskToken(data []byte) []byte { if len(data) != tokenLength { return nil } // tokenLength*2 == len(enckey + token) result := make([]byte, 2*tokenLength) // the first half of the result is the OTP // the second half is the masked token itself key := result[:tokenLength] token := result[tokenLength:] copy(token, data) // generate the random token if _, err := io.ReadFull(rand.Reader, key); err != nil { panic(err) } oneTimePad(token, key) return result } // end: vendored from nosurf var indexHtml = template.Must(template.New("index").Parse(`
`)) func main() { m := http.NewServeMux() m.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { unmaskedToken := generateToken() maskedToken := maskToken(unmaskedToken) http.SetCookie(w, &http.Cookie{ Name: "csrf_token", Value: b64encode(unmaskedToken), Domain: "target.localhost", Path: "/mutate", MaxAge: 3600, }) err := indexHtml.Execute(w, struct { Token string }{ b64encode(maskedToken), }) if err != nil { panic(err) } }) m.HandleFunc("POST /mutate", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Mutating request successful.")) }) if err := http.ListenAndServe(":5001", m); err != nil { panic(err) } }