go-dvote/service/censusmanager.go
service/censusmanager.goRaw
1package censusmanager
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7 "net/http"
8 "strconv"
9 "strings"
10 "time"
11
12 "github.com/vocdoni/dvote-census/tree"
13 "github.com/vocdoni/dvote-relay/crypto/signature"
14)
15
16const authTimeWindow = 10 // Time window (seconds) in which TimeStamp will be accepted if auth enabled
17
18var authPubKey string
19
20var T tree.Tree // MerkleTree dvote-census library
21var S signature.SignKeys // Signature dvote-relay library
22
23type Claim struct {
24 CensusID string `json:"censusID"` // References to MerkleTree namespace
25 ClaimData string `json:"claimData"` // Data to add to the MerkleTree
26 ProofData string `json:"proofData"` // MerkleProof to check
27 TimeStamp string `json:"timeStamp"` // Unix TimeStamp in seconds
28 Signature string `json:"signature"` // Signature as Hexadecimal String
29}
30
31type Result struct {
32 Error bool `json:"error"`
33 Response string `json:"response"`
34}
35
36func reply(resp *Result, w http.ResponseWriter) {
37 err := json.NewEncoder(w).Encode(resp)
38 if err != nil {
39 http.Error(w, err.Error(), 500)
40 } else {
41 w.Header().Set("content-type", "application/json")
42 }
43}
44
45func checkRequest(w http.ResponseWriter, req *http.Request) bool {
46 if req.Body == nil {
47 http.Error(w, "Please send a request body", 400)
48 return false
49 }
50 return true
51}
52
53func checkAuth(timestamp, signature, message string) bool {
54 if len(authPubKey) < 1 {
55 return true
56 }
57 currentTime := int64(time.Now().Unix())
58 timeStampRemote, err := strconv.ParseInt(timestamp, 10, 32)
59 if err != nil {
60 log.Printf("Cannot parse timestamp data %s\n", err)
61 return false
62 }
63 if timeStampRemote < currentTime+authTimeWindow &&
64 timeStampRemote > currentTime-authTimeWindow {
65 v, err := S.Verify(message, signature, authPubKey)
66 if err != nil {
67 log.Printf("Verification error: %s\n", err)
68 }
69 return v
70 }
71 return false
72}
73
74func addCorsHeaders(w *http.ResponseWriter, req *http.Request) {
75 (*w).Header().Set("Access-Control-Allow-Origin", "*")
76 (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
77 (*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
78}
79
80func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
81 addCorsHeaders(&w, req)
82
83 if (*req).Method == "OPTIONS" {
84 return
85 }
86
87 var c Claim
88 var resp Result
89 if ok := checkRequest(w, req); !ok {
90 return
91 }
92 // Decode JSON
93 err := json.NewDecoder(req.Body).Decode(&c)
94 if err != nil {
95 http.Error(w, err.Error(), 400)
96 return
97 }
98
99 // Process data
100 log.Printf("Received censusID:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n",
101 c.CensusID, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature)
102 resp.Error = false
103 resp.Response = ""
104
105 if len(c.CensusID) > 0 {
106 T.Namespace = c.CensusID
107 } else {
108 resp.Error = true
109 resp.Response = "censusID is not valid"
110 reply(&resp, w)
111 return
112 }
113
114 if len(c.ClaimData) < 0 {
115 resp.Error = true
116 resp.Response = "data not valid"
117 reply(&resp, w)
118 return
119 }
120
121 if op == "add" {
122 msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
123 if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
124 if strings.HasPrefix(c.CensusID, "0x") {
125 resp.Error = true
126 resp.Response = "add claim to snapshot is not allowed"
127 } else {
128 err = T.AddClaim([]byte(c.ClaimData))
129 }
130 } else {
131 resp.Error = true
132 resp.Response = "invalid authentication"
133 }
134 }
135
136 if op == "gen" {
137 resp.Response, err = T.GenProof([]byte(c.ClaimData))
138 }
139
140 if op == "root" {
141 resp.Response = T.GetRoot()
142 }
143
144 if op == "dump" {
145 values, err := T.Dump()
146 if err != nil {
147 resp.Error = true
148 resp.Response = fmt.Sprint(err)
149 } else {
150 jValues, err := json.Marshal(values)
151 if err != nil {
152 resp.Error = true
153 resp.Response = fmt.Sprint(err)
154 } else {
155 resp.Response = string(jValues)
156 }
157 }
158 }
159
160 if op == "snapshot" {
161 msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
162 if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
163 if strings.HasPrefix(c.CensusID, "0x") {
164 resp.Error = true
165 resp.Response = "snapshot an snapshot makes no sense"
166 } else {
167 snapshotNamespace, err := T.Snapshot()
168 if err != nil {
169 resp.Error = true
170 resp.Response = fmt.Sprint(err)
171 } else {
172 resp.Response = snapshotNamespace
173 }
174 }
175 } else {
176 resp.Error = true
177 resp.Response = "invalid authentication"
178 }
179 }
180
181 if op == "check" {
182 if len(c.ProofData) < 1 {
183 resp.Error = true
184 resp.Response = "proofData not provided"
185 } else {
186 validProof, _ := T.CheckProof([]byte(c.ClaimData), c.ProofData)
187 if validProof {
188 resp.Response = "valid"
189 } else {
190 resp.Response = "invalid"
191 }
192 }
193 }
194
195 reply(&resp, w)
196}
197
198func Listen(port int, proto string, pubKey string) {
199 srv := &http.Server{
200 Addr: fmt.Sprintf(":%d", port),
201 ReadHeaderTimeout: 4 * time.Second,
202 ReadTimeout: 4 * time.Second,
203 WriteTimeout: 4 * time.Second,
204 IdleTimeout: 3 * time.Second,
205 }
206
207 http.HandleFunc("/addClaim", func(w http.ResponseWriter, r *http.Request) {
208 claimHandler(w, r, "add")
209 })
210 http.HandleFunc("/genProof", func(w http.ResponseWriter, r *http.Request) {
211 claimHandler(w, r, "gen")
212 })
213 http.HandleFunc("/checkProof", func(w http.ResponseWriter, r *http.Request) {
214 claimHandler(w, r, "check")
215 })
216 http.HandleFunc("/getRoot", func(w http.ResponseWriter, r *http.Request) {
217 claimHandler(w, r, "root")
218 })
219 http.HandleFunc("/snapshot", func(w http.ResponseWriter, r *http.Request) {
220 claimHandler(w, r, "snapshot")
221 })
222 http.HandleFunc("/dump", func(w http.ResponseWriter, r *http.Request) {
223 claimHandler(w, r, "dump")
224 })
225
226 if len(pubKey) > 1 {
227 log.Printf("Enabling signature authentication with %s\n", pubKey)
228 authPubKey = pubKey
229 } else {
230 authPubKey = ""
231 }
232
233 if proto == "https" {
234 log.Print("Starting server in https mode")
235 if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil {
236 panic(err)
237 }
238 }
239 if proto == "http" {
240 log.Print("Starting server in http mode")
241 srv.SetKeepAlivesEnabled(false)
242 if err := srv.ListenAndServe(); err != nil {
243 panic(err)
244 }
245 }
246}