// 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"
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
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
}
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())
}
}
// 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
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
}
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
}
--- /dev/null
+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))
+}
--- /dev/null
+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
+}