|
|
package censusmanager
import ( "encoding/json" "fmt" "log" "net/http" "strconv" "time"
signature "github.com/vocdoni/go-dvote/crypto/signature_ecdsa" tree "github.com/vocdoni/go-dvote/tree" )
// Time window (seconds) in which TimeStamp will be accepted if auth enabled
const authTimeWindow = 10
// MkTrees map of merkle trees indexed by censusId
var MkTrees map[string]*tree.Tree
// Signatures map of management pubKeys indexed by censusId
var Signatures map[string]string
var currentSignature signature.SignKeys
// Claim type represents a JSON object with all possible fields
type Claim struct { Method string `json:"method"` // method to call
CensusID string `json:"censusId"` // References to MerkleTree namespace
RootHash string `json:"rootHash"` // References to MerkleTree rootHash
ClaimData string `json:"claimData"` // Data to add to the MerkleTree
ProofData string `json:"proofData"` // MerkleProof to check
TimeStamp string `json:"timeStamp"` // Unix TimeStamp in seconds
Signature string `json:"signature"` // Signature as Hexadecimal String
}
// Result type represents a JSON object with the result of the method executed
type Result struct { Error bool `json:"error"` Response string `json:"response"` }
// AddNamespace adds a new merkletree identified by a censusId (name)
func AddNamespace(name, pubKey string) { if len(MkTrees) == 0 { MkTrees = make(map[string]*tree.Tree) } if len(Signatures) == 0 { Signatures = make(map[string]string) }
mkTree := tree.Tree{} mkTree.Init(name) MkTrees[name] = &mkTree Signatures[name] = pubKey }
func reply(resp *Result, w http.ResponseWriter) { err := json.NewEncoder(w).Encode(resp) if err != nil { http.Error(w, err.Error(), 500) } else { w.Header().Set("content-type", "application/json") } }
func checkRequest(w http.ResponseWriter, req *http.Request) bool { if req.Body == nil { http.Error(w, "Please send a request body", 400) return false } return true }
func checkAuth(timestamp, signed, pubKey, message string) bool { if len(pubKey) < 1 { return true } currentTime := int64(time.Now().Unix()) timeStampRemote, err := strconv.ParseInt(timestamp, 10, 32) if err != nil { log.Printf("Cannot parse timestamp data %s\n", err) return false } if timeStampRemote < currentTime+authTimeWindow && timeStampRemote > currentTime-authTimeWindow { v, err := currentSignature.Verify(message, signed, pubKey) if err != nil { log.Printf("Verification error: %s\n", err) } return v } return false }
func httpHandler(w http.ResponseWriter, req *http.Request) { var c Claim if ok := checkRequest(w, req); !ok { return } // Decode JSON
err := json.NewDecoder(req.Body).Decode(&c) if err != nil { http.Error(w, err.Error(), 400) return } if len(c.Method) < 1 { http.Error(w, "method must be specified", 400) return } resp := opHandler(&c) reply(resp, w) }
func opHandler(c *Claim) *Result { var resp Result op := c.Method var err error
// Process data
log.Printf("censusId:{%s} method:{%s} rootHash:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n", c.CensusID, c.Method, c.RootHash, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature) authString := fmt.Sprintf("%s%s%s%s%s", c.Method, c.CensusID, c.RootHash, c.ClaimData, c.TimeStamp) resp.Error = false resp.Response = "" censusFound := false if len(c.CensusID) > 0 { _, censusFound = MkTrees[c.CensusID] } if !censusFound { resp.Error = true resp.Response = "censusId not valid or not found" return &resp }
//Methods without rootHash
if op == "getRoot" { resp.Response = MkTrees[c.CensusID].GetRoot() return &resp }
if op == "addClaim" { if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], authString); auth { err = MkTrees[c.CensusID].AddClaim([]byte(c.ClaimData)) } else { resp.Error = true resp.Response = "invalid authentication" } return &resp }
//Methods with rootHash, if rootHash specified snapshot the tree
var t *tree.Tree if len(c.RootHash) > 1 { //if rootHash specified
t, err = MkTrees[c.CensusID].Snapshot(c.RootHash) if err != nil { log.Printf("Snapshot error: %s", err.Error()) resp.Error = true resp.Response = "invalid root hash" return &resp } } else { //if rootHash not specified use current tree
t = MkTrees[c.CensusID] }
if op == "genProof" { resp.Response, err = t.GenProof([]byte(c.ClaimData)) if err != nil { resp.Error = true resp.Response = err.Error() } return &resp }
if op == "getIdx" { resp.Response, err = t.GetIndex([]byte(c.ClaimData)) return &resp }
if op == "dump" { if auth := checkAuth(c.TimeStamp, c.Signature, Signatures[c.CensusID], authString); !auth { resp.Error = true resp.Response = "invalid authentication" return &resp } //dump the claim data and return it
values, err := t.Dump() if err != nil { resp.Error = true resp.Response = err.Error() } else { jValues, err := json.Marshal(values) if err != nil { resp.Error = true resp.Response = err.Error() } else { resp.Response = fmt.Sprintf("%s", jValues) } } return &resp }
if op == "checkProof" { if len(c.ProofData) < 1 { resp.Error = true resp.Response = "proofData not provided" return &resp } // Generate proof and return it
validProof, err := t.CheckProof([]byte(c.ClaimData), c.ProofData) if err != nil { resp.Error = true resp.Response = err.Error() return &resp } if validProof { resp.Response = "valid" } else { resp.Response = "invalid" } return &resp }
return &resp }
func addCorsHeaders(w *http.ResponseWriter, req *http.Request) { (*w).Header().Set("Access-Control-Allow-Origin", "*") (*w).Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") (*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") }
// Listen starts a census service defined of type "proto"
func Listen(port int, proto string) { srv := &http.Server{ Addr: fmt.Sprintf(":%d", port), ReadHeaderTimeout: 4 * time.Second, ReadTimeout: 4 * time.Second, WriteTimeout: 4 * time.Second, IdleTimeout: 3 * time.Second, }
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { addCorsHeaders(&w, r) if r.Method == http.MethodPost { httpHandler(w, r) } else if r.Method != http.MethodOptions { http.Error(w, "Not found", http.StatusNotFound) } }) if proto == "https" { log.Print("Starting server in https mode") if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil { panic(err) } } if proto == "http" { log.Print("Starting server in http mode") srv.SetKeepAlivesEnabled(false) if err := srv.ListenAndServe(); err != nil { panic(err) } } }
|