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