arnaucube
arnaucube/go-dvotePublic Self-hosted repository
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} method:{%s} rootHash:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n",
122		c.CensusID, c.Method, c.RootHash, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature)
123	authString := fmt.Sprintf("%s%s%s%s%s", c.Method, 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}