ugly server implementation without signature checks
authornsz <nsz@port70.net>
Thu, 24 Nov 2011 20:17:02 +0000 (21:17 +0100)
committernsz <nsz@port70.net>
Thu, 24 Nov 2011 20:17:02 +0000 (21:17 +0100)
document/document.go
epoint-server.go [new file with mode: 0644]
logic/Makefile [new file with mode: 0644]
logic/logic.go [new file with mode: 0644]

index e2a0859..695bc86 100644 (file)
@@ -31,6 +31,7 @@ package document
 // TODO: trailing space handling in ParseFields
 // TODO: fields of notice (last notice, serial, failure notice,..)
 // TODO: limits and cert type specific input validation
+// TODO: fix Cert mess
 
 import (
        "bytes"
@@ -139,6 +140,27 @@ type Notice struct {
        References   []string // may be empty (startup notice)
 }
 
+type Cert struct {
+       IsDebit          bool
+       Holder           string
+       Serial           int64
+       Balance          int64
+       Denomination     string
+       Issuer           string
+       Date             int64
+       Difference       int64
+       Draft            string
+       Beneficiary      *string // only in debit cert
+       Drawer           *string // only in credit cert
+       DebitCert        *string // only in credit cert
+       AuthorizedBy     string
+       Notes            *string // optional
+       LastDebitSerial  int64   // 0 if none
+       LastCreditSerial int64   // 0 if none
+       LastCert         *string // nil if serial == 1
+       References       []string
+}
+
 type DebitCert struct {
        Holder           string
        Serial           int64
@@ -153,7 +175,7 @@ type DebitCert struct {
        Notes            *string // optional
        LastDebitSerial  int64   // 0 if none
        LastCreditSerial int64   // 0 if none
-       LastCert         *string // ? if serial == 1
+       LastCert         *string // nil if serial == 1
        References       []string
 }
 
@@ -187,10 +209,57 @@ type BounceCert struct {
        References   []string
 }
 
-// sha1 sum of the (cleaned) document as uppercase hex string
-func Id(d []byte) string {
+func ToCert(v interface{}) (cert *Cert, err error) {
+       cert = new(Cert)
+       switch x := v.(type) {
+       case *DebitCert:
+               cert.IsDebit = true
+               cert.Beneficiary = &x.Beneficiary
+
+               cert.Holder = x.Holder
+               cert.Serial = x.Serial
+               cert.Balance = x.Balance
+               cert.Denomination = x.Denomination
+               cert.Issuer = x.Issuer
+               cert.Date = x.Date
+               cert.Difference = x.Difference
+               cert.Draft = x.Draft
+               cert.AuthorizedBy = x.AuthorizedBy
+               cert.Notes = x.Notes
+               cert.LastDebitSerial = x.LastDebitSerial
+               cert.LastCreditSerial = x.LastCreditSerial
+               cert.LastCert = x.LastCert
+               cert.References = x.References
+
+       case *CreditCert:
+               cert.IsDebit = false
+               cert.Drawer = &x.Drawer
+               cert.DebitCert = &x.DebitCert
+
+               cert.Holder = x.Holder
+               cert.Serial = x.Serial
+               cert.Balance = x.Balance
+               cert.Denomination = x.Denomination
+               cert.Issuer = x.Issuer
+               cert.Date = x.Date
+               cert.Difference = x.Difference
+               cert.Draft = x.Draft
+               cert.AuthorizedBy = x.AuthorizedBy
+               cert.Notes = x.Notes
+               cert.LastDebitSerial = x.LastDebitSerial
+               cert.LastCreditSerial = x.LastCreditSerial
+               cert.LastCert = x.LastCert
+               cert.References = x.References
+       default:
+               err = fmt.Errorf("ToCert: only debit or credit document can be converted to cert")
+       }
+       return
+}
+
+// sha1 sum of the (cleaned) document body as uppercase hex string
+func Id(c *Signed) string {
        h := sha1.New()
-       h.Write(d)
+       h.Write(c.Body)
        return fmt.Sprintf("%040X", h.Sum())
 }
 
@@ -209,7 +278,7 @@ func Parse(s []byte) (iv interface{}, c *Signed, err error) {
 }
 
 // format and sign an epoint document
-func Format(iv interface{}, key *openpgp.Entity) (s []byte, err error) {
+func Format(iv interface{}, key *openpgp.Entity) (s []byte, c *Signed, err error) {
        doc, err := FormatStruct(iv)
        if err != nil {
                return
@@ -218,31 +287,21 @@ func Format(iv interface{}, key *openpgp.Entity) (s []byte, err error) {
        if err != nil {
                return
        }
-       c, err := Sign(body, key)
+       c, err = Sign(body, key)
        if err != nil {
                return
        }
-       return FormatSigned(c)
+       s, err = FormatSigned(c)
+       return
 }
 
 // verify an epoint document, return the cleaned version as well
-func Verify(c *Signed, key openpgp.KeyRing) (cleaned []byte, err error) {
-       err = CleanSigned(c)
-       if err != nil {
-               return
-       }
-       err = VerifyCleaned(c, key)
-       if err != nil {
-               return
-       }
-       return FormatSigned(c)
-}
-
-// verify signature of body with given key
-func VerifyCleaned(c *Signed, key openpgp.KeyRing) (err error) {
+func Verify(c *Signed, key openpgp.KeyRing) (err error) {
        msg := bytes.NewBuffer(c.Body)
        sig := bytes.NewBuffer(c.Signature)
-       _, err = openpgp.CheckArmoredDetachedSignature(key, msg, sig)
+// TODO: verify signature
+       _,_ = msg,sig
+//     _, err = openpgp.CheckArmoredDetachedSignature(key, msg, sig)
        return
 }
 
@@ -252,7 +311,8 @@ func Sign(body []byte, key *openpgp.Entity) (c *Signed, err error) {
        c.Hash = "SHA256"
        c.Body = body
        w := new(bytes.Buffer)
-       err = openpgp.ArmoredDetachSignText(w, key, bytes.NewBuffer(c.Body))
+       w.Write([]byte("\n-----BEGIN PGP SIGNATURE-----\n\nTODO: signature\n"))
+//     err = openpgp.ArmoredDetachSignText(w, key, bytes.NewBuffer(c.Body))
        c.Signature = w.Bytes()
        return
 }
diff --git a/epoint-server.go b/epoint-server.go
new file mode 100644 (file)
index 0000000..8ceef64
--- /dev/null
@@ -0,0 +1,128 @@
+package main
+
+import (
+       "crypto/openpgp"
+       "epoint/logic"
+       "fmt"
+       "log"
+       "net/http"
+       "os"
+)
+
+const (
+       port    = ":8080"
+       rootdir = "teststore"
+       seckey  = "./key.sec"
+)
+
+var serverkey *openpgp.Entity
+
+// todo: http header limit: 64K, body limit: 64K
+
+// getPubkey(db, fpr) (pk, err)
+// putPubkey(db, fpr, pk) (err)
+// getDraft(db, id) (draft, err)
+// putDraft(db, id, draft) (err)
+
+// getCert(db, id) (cert, err)
+// putCert(db, id, cert) (err)
+// getCertBySerial(db, fpr, sn) (cert, err)
+// getCertByFpr(db, fpr) (cert, err)
+
+// todo: update cert (inc serial..)
+
+// Dummy initialization of serverkey
+func initkey() (err error) {
+       f, err := os.Open(seckey)
+       if err != nil {
+               return
+       }
+       keys, err := openpgp.ReadKeyRing(f)
+       if err != nil {
+               f.Close()
+               return
+       }
+       err = f.Close()
+       if err != nil {
+               return
+       }
+       serverkey = keys[0]
+       err = os.MkdirAll(rootdir, 0755)
+       if err != nil {
+               return
+       }
+       f, err = os.Create(rootdir + "/serverkey")
+       if err != nil {
+               return
+       }
+       err = serverkey.Serialize(f)
+       if err != nil {
+               return
+       }
+       // TODO: make sure pubkey is replicated and available
+       err = f.Sync()
+       if err != nil {
+               return
+       }
+       err = f.Close()
+       return
+}
+
+func defaultHandler(w http.ResponseWriter, r *http.Request) {
+       log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL.Raw)
+       fmt.Fprintf(w, "not implemented: %s %s\n", r.Method, r.URL.Raw)
+}
+
+func submitHandler(w http.ResponseWriter, r *http.Request) {
+       log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL.Raw)
+       draft := r.FormValue("draft")
+       debit := r.FormValue("debit")
+       if len(draft) > 0 {
+               cert, err := logic.EvalDraft([]byte(draft), serverkey)
+               if err != nil {
+                       log.Printf("eval draft fail: %s", err)
+                       fmt.Fprintf(w, "eval draft fail: %s\n", err)
+               } else {
+                       w.Write(cert)
+               }
+       } else if len(debit) > 0 {
+               cert, err := logic.EvalDebitCert([]byte(debit), serverkey)
+               if err != nil {
+                       log.Printf("eval debit fail: %s", err)
+                       fmt.Fprintf(w, "eval debit fail: %s\n", err)
+               } else {
+                       w.Write(cert)
+               }
+       } else {
+               fmt.Fprintf(w, "expected draft or debit param, got: %s %s\n", r.Method, r.URL.Raw)
+       }
+}
+
+func main() {
+       err := initkey()
+       if err != nil {
+               log.Fatal(err)
+       }
+       err = logic.Init()
+       if err != nil {
+               log.Fatal(err)
+       }
+
+       http.HandleFunc("/", defaultHandler)
+
+       // queries
+       http.HandleFunc("/serverkey", func(w http.ResponseWriter, r *http.Request) {
+               http.ServeFile(w, r, rootdir+"/serverkey")
+       })
+//     http.HandleFunc("/status", defaultHandler)
+//     http.HandleFunc("/pubkey", defaultHandler)
+//     http.HandleFunc("/daft", defaultHandler)
+//     http.HandleFunc("/cert", defaultHandler)
+
+       // actions
+       // withdraw, draw, deposit, process, clear
+       http.HandleFunc("/submit", submitHandler)
+
+       log.Printf("start service, server id: %X\n", serverkey.PrimaryKey.Fingerprint)
+       log.Fatal(http.ListenAndServe(port, nil))
+}
diff --git a/logic/Makefile b/logic/Makefile
new file mode 100644 (file)
index 0000000..89942c5
--- /dev/null
@@ -0,0 +1,6 @@
+include $(GOROOT)/src/Make.inc
+
+TARG=epoint/logic
+GOFILES=logic.go
+
+include $(GOROOT)/src/Make.pkg
diff --git a/logic/logic.go b/logic/logic.go
new file mode 100644 (file)
index 0000000..a9fd15b
--- /dev/null
@@ -0,0 +1,420 @@
+package logic
+
+// main transfer logic
+
+import (
+       "bytes"
+       "crypto/openpgp"
+       "epoint/document"
+       "epoint/store"
+       "fmt"
+       "time"
+)
+
+// TODO: do in docs?
+const IntLimit = 1e15
+
+var db *store.Conn
+
+func AddKeys(d []byte) (err error) {
+       entities, err := openpgp.ReadArmoredKeyRing(bytes.NewBuffer(d))
+       if err != nil {
+               return
+       }
+       // TODO: allow multiple key uploads at once?
+       if len(entities) > 100 {
+               err = fmt.Errorf("expected at most 100 keys; got %d", len(entities))
+               return
+       }
+       for _, e := range entities {
+               // TODO: various checks..
+               b := new(bytes.Buffer)
+               err = e.Serialize(b)
+               if err != nil {
+                       return
+               }
+               fpr := fmt.Sprintf("%040X", e.PrimaryKey.Fingerprint)
+               err = db.Set("key", fpr, b.Bytes())
+               if err != nil {
+                       return
+               }
+               err = db.Append("fprlist/64", fpr[len(fpr)-16:], []byte(fpr))
+               if err != nil {
+                       return
+               }
+               err = db.Append("fprlist/32", fpr[len(fpr)-8:], []byte(fpr))
+               if err != nil {
+                       return
+               }
+       }
+       return
+}
+
+func CertByDraft(draftid string) (d []byte, err error) {
+       certid, err := db.Get("certid/debit", draftid)
+       if err != nil {
+               // TODO: we have the draft but the cert is not ready
+               return
+       }
+       d, err = db.Get("cert", string(certid))
+       if err != nil {
+               // shouldn't happen, cert is not available
+               return
+       }
+       return
+}
+
+func CertByDebitCert(debitid string) (d []byte, err error) {
+       creditid, err := db.Get("certid/credit", debitid)
+       if err != nil {
+               // TODO: we have the debit cert but the credit cert is not ready
+               return
+       }
+       d, err = db.Get("cert", string(creditid))
+       if err != nil {
+               // shouldn't happen, cert is not available
+               return
+       }
+       return
+}
+
+// parse clear signed draft and verify it
+func ParseDraft(d []byte) (draft *document.Draft, draftid string, err error) {
+       iv, signed, err := document.Parse(d)
+       if err != nil {
+               return
+       }
+       draft, ok := iv.(*document.Draft)
+       if !ok {
+               err = fmt.Errorf("ParseDraft: expected a draft docuent")
+               return
+       }
+       draftid = document.Id(signed)
+/*
+       k, err := db.Get("key", draft.Drawer)
+       if err != nil {
+               return
+       }
+       kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k))
+       if err != nil {
+               // internal error: pubkey cannot be parsed
+               return
+       }
+       cleaned, err = document.Verify(signed, kr)
+       if err != nil {
+               return
+       }
+       // TODO: verify issuer
+       _, err = db.Get("key", draft.Beneficiary)
+       if err != nil {
+               return
+       }
+*/
+       // TODO: do various format checks (AuthorizedBy check etc)
+       if draft.Amount <= 0 || draft.Amount >= IntLimit {
+               err = fmt.Errorf("draft amount is invalid: %d", draft.Amount)
+               return
+       }
+       return
+}
+
+func ParseDebitCert(d []byte) (cert *document.DebitCert, certid string, err error) {
+       iv, signed, err := document.Parse(d)
+       if err != nil {
+               return
+       }
+       cert, ok := iv.(*document.DebitCert)
+       if !ok {
+               err = fmt.Errorf("ParseDebitCert: expected a debit docuent")
+               return
+       }
+/*
+       // TODO: keep our key at hand
+       k, err := db.Get("key", cert.AuthorizedBy)
+       if err != nil {
+               return
+       }
+       kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k))
+       if err != nil {
+               // internal error: pubkey cannot be parsed
+               return
+       }
+       // must clean up to make sure the hash is ok
+       cleaned, err = document.Verify(signed, kr)
+       if err != nil {
+               return
+       }
+*/
+       certid = document.Id(signed)
+       return
+}
+
+func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, error) {
+       cert := new(document.DebitCert)
+       cert.Holder = draft.Drawer
+       cert.Date = time.Seconds()
+       cert.Denomination = "epoint"
+       cert.Issuer = draft.Issuer
+       cert.AuthorizedBy = draft.AuthorizedBy
+       cert.Difference = -draft.Amount
+       cert.Draft = draftid
+       cert.Beneficiary = draft.Beneficiary
+
+       oid, err := db.Get("certid/last", draft.Drawer)
+       oldcertid := string(oid)
+       if err != nil {
+               // first cert: drawer is issuer
+               if draft.Drawer != draft.Issuer {
+                       return nil, fmt.Errorf("drawer must be the issuer when drawing an empty account")
+               }
+               cert.Serial = 1
+               cert.Balance = cert.Difference
+               cert.LastDebitSerial = 0
+               cert.LastCreditSerial = 0
+       } else {
+               d, err := db.Get("cert", oldcertid)
+               if err != nil {
+                       return nil, err
+               }
+               iv, _, err := document.Parse(d)
+               if err != nil {
+                       // internal error
+                       return nil, err
+               }
+               // TODO: this is a hack
+               oldcert, err := document.ToCert(iv)
+               // TODO: sanity checks? oldcert.Holder == draft.Drawer
+               cert.Serial = oldcert.Serial + 1
+               cert.Balance = oldcert.Balance + cert.Difference
+               if cert.Balance <= -IntLimit {
+                       return nil, fmt.Errorf("balance limit exceeded: %d", cert.Balance)
+               }
+               if oldcert.Balance > 0 && cert.Balance < 0 {
+                       return nil, fmt.Errorf("insufficient funds: %d", oldcert.Balance)
+               }
+               cert.LastDebitSerial = oldcert.LastDebitSerial
+               cert.LastCreditSerial = oldcert.LastCreditSerial
+               if oldcert.IsDebit {
+                       cert.LastDebitSerial = oldcert.Serial
+               } else {
+                       cert.LastCreditSerial = oldcert.Serial
+               }
+               cert.LastCert = &oldcertid
+       }
+       return cert, nil
+}
+
+func NewCreditCert(draftid string, draft *document.Draft, dcertid string, dcert *document.DebitCert) (*document.CreditCert, error) {
+       cert := new(document.CreditCert)
+       // TODO: get from old cert instead?
+       cert.Holder = dcert.Beneficiary
+       cert.Date = time.Seconds()
+       // TODO: get these from the cert holder pubkey
+       cert.Denomination = "epoint"
+       cert.Issuer = draft.Issuer
+       cert.AuthorizedBy = dcert.AuthorizedBy // TODO: draft vs dcert vs serverside decision
+       cert.Difference = -dcert.Difference
+       cert.Draft = draftid
+       cert.Drawer = dcert.Holder
+       cert.DebitCert = dcertid
+
+       oid, err := db.Get("certid/last", dcert.Beneficiary)
+       oldcertid := string(oid)
+       if err != nil {
+               // this is the first cert
+               cert.Serial = 1
+               cert.Balance = cert.Difference
+               cert.LastDebitSerial = 0
+               cert.LastCreditSerial = 0
+       } else {
+               d, err := db.Get("cert", oldcertid)
+               if err != nil {
+                       // internal error
+                       return nil, err
+               }
+               iv, _, err := document.Parse(d)
+               if err != nil {
+                       // internal error
+                       return nil, err
+               }
+               // TODO: this is a hack
+               oldcert, err := document.ToCert(iv)
+               if err != nil {
+                       // internal error
+                       return nil, err
+               }
+               cert.Serial = oldcert.Serial + 1
+               cert.Balance = oldcert.Balance + cert.Difference
+               if cert.Balance >= IntLimit {
+                       return nil, fmt.Errorf("balance limit exceeded: %d", cert.Balance)
+               }
+               cert.LastDebitSerial = oldcert.LastDebitSerial
+               cert.LastCreditSerial = oldcert.LastCreditSerial
+               if oldcert.IsDebit {
+                       cert.LastDebitSerial = oldcert.Serial
+               } else {
+                       cert.LastCreditSerial = oldcert.Serial
+               }
+               cert.LastCert = &oldcertid
+       }
+       return cert, nil
+}
+
+// TODO: draft ref
+func EvalDraft(d []byte, sk *openpgp.Entity) (r []byte, err error) {
+       draft, draftid, err := ParseDraft(d)
+       if err != nil {
+               return
+       }
+       _, err = db.Get("draft", draftid)
+       if err == nil {
+               // found
+               return CertByDraft(draftid)
+       }
+       // if draft is ok we save it
+       err = db.Set("draft", draftid, d)
+       if err != nil {
+               // internal error
+               return
+       }
+
+       // debit cert
+       cert, err := NewDebitCert(draftid, draft)
+       if err != nil {
+               return
+       }
+       r, signed, err := document.Format(cert, sk)
+       certid := document.Id(signed)
+       err = db.Set("cert", certid, r)
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certid/debit", draftid, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certid/last", cert.Holder, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       // TODO: append?
+       err = db.Set("certid/all", fmt.Sprintf("%s.%09d", cert.Holder, cert.Serial), []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       return
+}
+
+func EvalDebitCert(d []byte, sk *openpgp.Entity) (r []byte, err error) {
+       dcert, dcertid, err := ParseDebitCert(d)
+       if err != nil {
+               return
+       }
+       r, err = CertByDebitCert(dcertid)
+       if err == nil {
+               // found
+               return
+       }
+       // TODO: we only need the draft to know the issuer (+beneficiary)
+       // it should be in the pubkey
+       d, err = db.Get("draft", dcert.Draft)
+       if err != nil {
+               // internal error
+               return
+       }
+       iv, _, err := document.Parse(d)
+       if err != nil {
+               // internal error
+               return
+       }
+       draft, ok := iv.(*document.Draft)
+       if !ok {
+               // internal error
+               err = fmt.Errorf("EvalDebitCert: expected draft from internal db")
+               return
+       }
+
+       // credit side
+       // TODO: check pubkey etc
+       cert, err := NewCreditCert(dcert.Draft, draft, dcertid, dcert)
+       if err != nil {
+               // internal error
+               return
+       }
+       r, signed, err := document.Format(cert, sk)
+       if err != nil {
+               // internal error
+               return
+       }
+       certid := document.Id(signed)
+       err = db.Set("cert", certid, r)
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certid/credit", dcertid, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certid/last", cert.Holder, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       // TODO: append?
+       err = db.Set("certid/all", fmt.Sprintf("%s.%09d", cert.Holder, cert.Serial), []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       return
+}
+
+func Init() (err error) {
+       db, err = store.Open("teststore")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("key")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("cert")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("draft")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("certid/credit")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("certid/debit")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("certid/last")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("certid/all")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("fprlist/64")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("fprlist/32")
+       if err != nil {
+               return
+       }
+       return
+}