You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
6.7 KiB

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)
}
}
}