package censusmanager
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/vocdoni/dvote-census/tree"
|
|
"github.com/vocdoni/dvote-relay/crypto/signature"
|
|
)
|
|
|
|
const authTimeWindow = 10 // Time window (seconds) in which TimeStamp will be accepted if auth enabled
|
|
|
|
var authPubKey string
|
|
|
|
var T tree.Tree // MerkleTree dvote-census library
|
|
var S signature.SignKeys // Signature dvote-relay library
|
|
|
|
type Claim struct {
|
|
CensusID string `json:"censusID"` // References to MerkleTree namespace
|
|
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
|
|
}
|
|
|
|
type Result struct {
|
|
Error bool `json:"error"`
|
|
Response string `json:"response"`
|
|
}
|
|
|
|
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, signature, message string) bool {
|
|
if len(authPubKey) < 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 := S.Verify(message, signature, authPubKey)
|
|
if err != nil {
|
|
log.Printf("Verification error: %s\n", err)
|
|
}
|
|
return v
|
|
}
|
|
return false
|
|
}
|
|
|
|
func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
|
|
var c Claim
|
|
var resp Result
|
|
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
|
|
}
|
|
|
|
// Process data
|
|
log.Printf("Received censusID:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n",
|
|
c.CensusID, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature)
|
|
resp.Error = false
|
|
resp.Response = ""
|
|
|
|
if len(c.CensusID) > 0 {
|
|
T.Namespace = c.CensusID
|
|
} else {
|
|
resp.Error = true
|
|
resp.Response = "censusID is not valid"
|
|
reply(&resp, w)
|
|
return
|
|
}
|
|
|
|
if len(c.ClaimData) < 0 {
|
|
resp.Error = true
|
|
resp.Response = "data not valid"
|
|
reply(&resp, w)
|
|
return
|
|
}
|
|
|
|
if op == "add" {
|
|
msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
|
|
if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
|
|
if strings.HasPrefix(c.CensusID, "0x") {
|
|
resp.Error = true
|
|
resp.Response = "add claim to snapshot is not allowed"
|
|
} else {
|
|
err = T.AddClaim([]byte(c.ClaimData))
|
|
}
|
|
} else {
|
|
resp.Error = true
|
|
resp.Response = "invalid authentication"
|
|
}
|
|
}
|
|
|
|
if op == "gen" {
|
|
resp.Response, err = T.GenProof([]byte(c.ClaimData))
|
|
}
|
|
|
|
if op == "root" {
|
|
resp.Response = T.GetRoot()
|
|
}
|
|
|
|
if op == "dump" {
|
|
values, err := T.Dump()
|
|
if err != nil {
|
|
resp.Error = true
|
|
resp.Response = fmt.Sprint(err)
|
|
} else {
|
|
jValues, err := json.Marshal(values)
|
|
if err != nil {
|
|
resp.Error = true
|
|
resp.Response = fmt.Sprint(err)
|
|
} else {
|
|
resp.Response = string(jValues)
|
|
}
|
|
}
|
|
}
|
|
|
|
if op == "snapshot" {
|
|
msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
|
|
if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
|
|
if strings.HasPrefix(c.CensusID, "0x") {
|
|
resp.Error = true
|
|
resp.Response = "snapshot an snapshot makes no sense"
|
|
} else {
|
|
snapshotNamespace, err := T.Snapshot()
|
|
if err != nil {
|
|
resp.Error = true
|
|
resp.Response = fmt.Sprint(err)
|
|
} else {
|
|
resp.Response = snapshotNamespace
|
|
}
|
|
}
|
|
} else {
|
|
resp.Error = true
|
|
resp.Response = "invalid authentication"
|
|
}
|
|
}
|
|
|
|
if op == "check" {
|
|
if len(c.ProofData) < 1 {
|
|
resp.Error = true
|
|
resp.Response = "proofData not provided"
|
|
} else {
|
|
validProof, _ := T.CheckProof([]byte(c.ClaimData), c.ProofData)
|
|
if validProof {
|
|
resp.Response = "valid"
|
|
} else {
|
|
resp.Response = "invalid"
|
|
}
|
|
}
|
|
}
|
|
|
|
reply(&resp, w)
|
|
}
|
|
|
|
func Listen(port int, proto string, pubKey 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("/addClaim", func(w http.ResponseWriter, r *http.Request) {
|
|
claimHandler(w, r, "add")
|
|
})
|
|
http.HandleFunc("/genProof", func(w http.ResponseWriter, r *http.Request) {
|
|
claimHandler(w, r, "gen")
|
|
})
|
|
http.HandleFunc("/checkProof", func(w http.ResponseWriter, r *http.Request) {
|
|
claimHandler(w, r, "check")
|
|
})
|
|
http.HandleFunc("/getRoot", func(w http.ResponseWriter, r *http.Request) {
|
|
claimHandler(w, r, "root")
|
|
})
|
|
http.HandleFunc("/snapshot", func(w http.ResponseWriter, r *http.Request) {
|
|
claimHandler(w, r, "snapshot")
|
|
})
|
|
http.HandleFunc("/dump", func(w http.ResponseWriter, r *http.Request) {
|
|
claimHandler(w, r, "dump")
|
|
})
|
|
|
|
if len(pubKey) > 1 {
|
|
log.Printf("Enabling signature authentication with %s\n", pubKey)
|
|
authPubKey = pubKey
|
|
} else {
|
|
authPubKey = ""
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|