arnaucube
arnaucube/go-dvotePublic Self-hosted repository
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 claimHandler(w http.ResponseWriter, req *http.Request, op string) {
 75	var c Claim
 76	var resp Result
 77	if ok := checkRequest(w, req); !ok {
 78		return
 79	}
 80	// Decode JSON
 81	err := json.NewDecoder(req.Body).Decode(&c)
 82	if err != nil {
 83		http.Error(w, err.Error(), 400)
 84		return
 85	}
 86
 87	// Process data
 88	log.Printf("Received censusID:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n",
 89		c.CensusID, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature)
 90	resp.Error = false
 91	resp.Response = ""
 92
 93	if len(c.CensusID) > 0 {
 94		T.Namespace = c.CensusID
 95	} else {
 96		resp.Error = true
 97		resp.Response = "censusID is not valid"
 98		reply(&resp, w)
 99		return
100	}
101
102	if len(c.ClaimData) < 0 {
103		resp.Error = true
104		resp.Response = "data not valid"
105		reply(&resp, w)
106		return
107	}
108
109	if op == "add" {
110		msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
111		if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
112			if strings.HasPrefix(c.CensusID, "0x") {
113				resp.Error = true
114				resp.Response = "add claim to snapshot is not allowed"
115			} else {
116				err = T.AddClaim([]byte(c.ClaimData))
117			}
118		} else {
119			resp.Error = true
120			resp.Response = "invalid authentication"
121		}
122	}
123
124	if op == "gen" {
125		resp.Response, err = T.GenProof([]byte(c.ClaimData))
126	}
127
128	if op == "root" {
129		resp.Response = T.GetRoot()
130	}
131
132	if op == "dump" {
133		values, err := T.Dump()
134		if err != nil {
135			resp.Error = true
136			resp.Response = fmt.Sprint(err)
137		} else {
138			jValues, err := json.Marshal(values)
139			if err != nil {
140				resp.Error = true
141				resp.Response = fmt.Sprint(err)
142			} else {
143				resp.Response = string(jValues)
144			}
145		}
146	}
147
148	if op == "snapshot" {
149		msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
150		if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
151			if strings.HasPrefix(c.CensusID, "0x") {
152				resp.Error = true
153				resp.Response = "snapshot an snapshot makes no sense"
154			} else {
155				snapshotNamespace, err := T.Snapshot()
156				if err != nil {
157					resp.Error = true
158					resp.Response = fmt.Sprint(err)
159				} else {
160					resp.Response = snapshotNamespace
161				}
162			}
163		} else {
164			resp.Error = true
165			resp.Response = "invalid authentication"
166		}
167	}
168
169	if op == "check" {
170		if len(c.ProofData) < 1 {
171			resp.Error = true
172			resp.Response = "proofData not provided"
173		} else {
174			validProof, _ := T.CheckProof([]byte(c.ClaimData), c.ProofData)
175			if validProof {
176				resp.Response = "valid"
177			} else {
178				resp.Response = "invalid"
179			}
180		}
181	}
182
183	reply(&resp, w)
184}
185
186func Listen(port int, proto string, pubKey string) {
187	srv := &http.Server{
188		Addr:              fmt.Sprintf(":%d", port),
189		ReadHeaderTimeout: 4 * time.Second,
190		ReadTimeout:       4 * time.Second,
191		WriteTimeout:      4 * time.Second,
192		IdleTimeout:       3 * time.Second,
193	}
194
195	http.HandleFunc("/addClaim", func(w http.ResponseWriter, r *http.Request) {
196		claimHandler(w, r, "add")
197	})
198	http.HandleFunc("/genProof", func(w http.ResponseWriter, r *http.Request) {
199		claimHandler(w, r, "gen")
200	})
201	http.HandleFunc("/checkProof", func(w http.ResponseWriter, r *http.Request) {
202		claimHandler(w, r, "check")
203	})
204	http.HandleFunc("/getRoot", func(w http.ResponseWriter, r *http.Request) {
205		claimHandler(w, r, "root")
206	})
207	http.HandleFunc("/snapshot", func(w http.ResponseWriter, r *http.Request) {
208		claimHandler(w, r, "snapshot")
209	})
210	http.HandleFunc("/dump", func(w http.ResponseWriter, r *http.Request) {
211		claimHandler(w, r, "dump")
212	})
213
214	if len(pubKey) > 1 {
215		log.Printf("Enabling signature authentication with %s\n", pubKey)
216		authPubKey = pubKey
217	} else {
218		authPubKey = ""
219	}
220
221	if proto == "https" {
222		log.Print("Starting server in https mode")
223		if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil {
224			panic(err)
225		}
226	}
227	if proto == "http" {
228		log.Print("Starting server in http mode")
229		srv.SetKeepAlivesEnabled(false)
230		if err := srv.ListenAndServe(); err != nil {
231			panic(err)
232		}
233	}
234}