package server // main transfer logic import ( "bytes" "crypto/openpgp" "epoint/document" "epoint/key" "epoint/store" "fmt" "log" "time" ) // TODO: do in docs? const IntLimit = 1e15 // transaction: either draft or debit cert type work struct { next *work docid string account string in []byte out []byte err error signed *document.Signed draft *document.Draft debit *document.DebitCert sync chan int } var db *store.Conn var serverkey *openpgp.Entity var newchan chan *work var delchan chan *work type worklist struct { head *work tail *work } func pushwork(ws *worklist, w *work) { if ws.tail == nil { ws.head = w ws.tail = w } else { ws.tail.next = w ws.tail = w } } func popwork(ws *worklist) *work { w := ws.head if w == nil { return nil } ws.head = w.next if ws.head == nil { ws.tail = nil } return w } func setserverkey(e *openpgp.Entity) (err error) { serverkey = e err = key.SelfSign(e) if err != nil { return } d, err := key.Format(e) if err != nil { return } err = db.Set("key", key.Id(e), d) if err != nil { return } err = db.Set("", "serverkey", d) return } func GetKeys(fpr string) (es openpgp.EntityList, err error) { b, err := db.Get("key", fpr) if err != nil { return } es, err = openpgp.ReadKeyRing(bytes.NewBuffer(b)) if err != nil { // internal error: pubkey cannot be parsed return } return } 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.. // TODO: collect errors instead of aborting addkeys isIssuer, issuer, denom, err1 := key.Check(e) err = err1 if err != nil { return } if !isIssuer { es, err1 := GetKeys(issuer) err = err1 if err != nil { return } ok, _, den, err1 := key.Check(es[0]) err = err1 if err != nil { // internal error return } if !ok || den != denom { err = fmt.Errorf("Issuer key check failed") return } } b := new(bytes.Buffer) err = e.Serialize(b) if err != nil { return } fpr := key.Id(e) err = db.Insert("key", fpr, b.Bytes()) if err != nil { return } err = db.Append("keysby/64", fpr[len(fpr)-16:], []byte(fpr)) if err != nil { return } err = db.Append("keysby/32", fpr[len(fpr)-8:], []byte(fpr)) if err != nil { return } } return } // Get cert through the named store func GetCert(name, id string) (d []byte, err error) { certid, err := db.Get(name, id) if err != nil { // ok if notfound return } d, err = db.Get("cert", string(certid)) if err != nil { // internal error: cert is not available return } return } func EvalDraft(d []byte) (c []byte, err error) { iv, signed, err := document.Parse(d) if err != nil { return } draft, ok := iv.(*document.Draft) if !ok { err = fmt.Errorf("EvalDraft: expected a draft document") return } k, err := db.Get("key", draft.Drawer) if err != nil { return } // TODO: key.Parse kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k)) if err != nil { // internal error: pubkey cannot be parsed return } err = document.Verify(signed, kr) if err != nil { return } // _, issuer, denom, err := key.Check(kr[0]) // 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 } k, err = db.Get("key", draft.Beneficiary) if err != nil { return } kr, err = openpgp.ReadKeyRing(bytes.NewBuffer(k)) if err != nil { // internal error: pubkey cannot be parsed return } return addDraft(d, signed, draft) } func EvalDebitCert(d []byte) (c []byte, 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 } kr := openpgp.EntityList{serverkey} if key.Id(serverkey) != cert.AuthorizedBy { // TODO: ... k := []byte(nil) 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 err = document.Verify(signed, kr) if err != nil { return } return addDebit(d, signed, cert) } func addDraft(d []byte, signed *document.Signed, draft *document.Draft) (c []byte, err error) { w := new(work) w.docid = document.Id(signed) w.account = fmt.Sprintf("%s.%s", draft.Drawer, draft.Issuer) w.in = d w.signed = signed w.draft = draft w.sync = make(chan int) log.Printf("add draft work: %s", w.account) newchan <- w <-w.sync return w.out, w.err } func addDebit(d []byte, signed *document.Signed, cert *document.DebitCert) (c []byte, err error) { w := new(work) w.docid = document.Id(signed) w.account = fmt.Sprintf("%s.%s", cert.Beneficiary, cert.Issuer) w.in = d w.signed = signed w.debit = cert w.sync = make(chan int) log.Printf("add debit work: %s", w.account) newchan <- w <-w.sync return w.out, w.err } func dispatch() { log.Printf("start dispatch") works := make(map[string]*worklist) for { select { case w := <-newchan: log.Printf("queue work: %s", w.account) ws := works[w.account] if ws == nil { // TODO: unnecessary alloc works[w.account] = new(worklist) go handle(w) } else { pushwork(ws, w) } case w := <-delchan: log.Printf("unqueue work: %s", w.account) ws := works[w.account] wnext := popwork(ws) if wnext == nil { delete(works, w.account) } else { go handle(wnext) } } } } func handle(w *work) { log.Printf("start work: %s", w.account) if w.debit != nil { w.out, w.err = handleDebit(w) } else if w.draft != nil { w.out, w.err = handleDraft(w) } else { panic("unreachable") } log.Printf("finish work: %s outlen: %d", w.account, len(w.out)) delchan <- w w.sync <- 0 } func handleDraft(w *work) (c []byte, err error) { nonce := fmt.Sprintf("%s.%s", w.account, w.draft.Nonce) oldid, err := db.Get("draftby/key.issuer.nonce", nonce) if err == nil { if string(oldid) != w.docid { err = fmt.Errorf("draft nonce is not unique (see draft %s)", oldid) } else { c, err = GetCert("certby/draft", w.docid) } return } else if _, ok := err.(store.NotFoundError); !ok { return } err = db.Begin(w.docid) if err != nil { return } err = db.Set("draft", w.docid, w.in) if err != nil { return } err = db.Set("draftby/key.issuer.nonce", nonce, []byte(w.docid)) if err != nil { return } cert, err := newDebitCert(w) if err != nil { // probably client error db.End(w.docid) return } c, signed, err := document.Format(cert, serverkey) if err != nil { return } certid := document.Id(signed) err = db.Set("cert", certid, c) if err != nil { // internal error return } err = db.Set("certby/draft", w.docid, []byte(certid)) if err != nil { // internal error return } // TODO: append? err = db.Set("certby/key.issuer.serial", fmt.Sprintf("%s.%09d", w.account, cert.Serial), []byte(certid)) if err != nil { // internal error return } err = db.Set("certby/key.issuer", w.account, []byte(certid)) if err != nil { // internal error return } // TODO: run journal cleanup in case of client errors err = db.End(w.docid) return } func handleDebit(w *work) (c []byte, err error) { c, err = GetCert("certby/debit", w.docid) if err == nil { return } else if _, ok := err.(store.NotFoundError); !ok { // internal error return } err = db.Begin(w.docid) if err != nil { return } err = db.Set("cert", w.docid, w.in) if err != nil { return } // TODO: check pubkey etc cert, err := newCreditCert(w) if err != nil { // internal error return } c, signed, err := document.Format(cert, serverkey) if err != nil { // internal error return } certid := document.Id(signed) err = db.Set("cert", certid, c) if err != nil { // internal error return } err = db.Set("certby/debit", w.docid, []byte(certid)) if err != nil { // internal error return } // TODO: append? err = db.Set("certby/key.issuer.serial", fmt.Sprintf("%s.%09d", w.account, cert.Serial), []byte(certid)) if err != nil { // internal error return } err = db.Set("certby/key.issuer", w.account, []byte(certid)) if err != nil { // internal error return } db.End(w.docid) return } func isIssuer(c *document.Cert) bool { return c.Issuer == c.Holder } func newDebitCert(w *work) (*document.DebitCert, error) { cert := new(document.DebitCert) cert.Holder = w.draft.Drawer cert.Date = time.Now().Unix() cert.Denomination = "epoint" cert.Issuer = w.draft.Issuer cert.AuthorizedBy = w.draft.AuthorizedBy cert.Difference = -w.draft.Amount cert.Draft = w.docid cert.Beneficiary = w.draft.Beneficiary oid, err := db.Get("certby/key.issuer", w.account) oldcertid := string(oid) if err != nil { // first cert: drawer is issuer if w.draft.Drawer != w.draft.Issuer { return nil, fmt.Errorf("drawer must be the issuer when drawing an empty account (%s != %s)", w.draft.Drawer, w.draft.Issuer) } 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: make sure oldcert and newcert cannot become inconsistent // TODO: this is a hack oldcert, err := document.ToCert(iv) if err != nil { // internal error return nil, err } // 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 !isIssuer(&cert.Cert) && cert.Balance < 0 { return nil, fmt.Errorf("insufficient funds: %d, draft: %d", oldcert.Balance, cert.Difference) } cert.LastDebitSerial = oldcert.LastDebitSerial cert.LastCreditSerial = oldcert.LastCreditSerial if _, ok := iv.(*document.DebitCert); ok { cert.LastDebitSerial = oldcert.Serial } else { cert.LastCreditSerial = oldcert.Serial } cert.LastCert = &oldcertid } return cert, nil } func newCreditCert(w *work) (*document.CreditCert, error) { cert := new(document.CreditCert) // TODO: get from old cert instead? cert.Holder = w.debit.Beneficiary cert.Date = time.Now().Unix() // TODO: get these from the cert holder pubkey cert.Denomination = "epoint" cert.Issuer = w.debit.Issuer cert.AuthorizedBy = w.debit.AuthorizedBy // TODO: draft vs dcert vs serverside decision cert.Difference = -w.debit.Difference cert.Draft = w.debit.Draft cert.Drawer = w.debit.Holder cert.DebitCert = w.docid oid, err := db.Get("certby/key.issuer", w.account) oldcertid := string(oid) if err != nil { if _, ok := err.(store.NotFoundError); !ok { // internal error return nil, err } // 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) } // sanity check if isIssuer(&cert.Cert) && cert.Balance > 0 { return nil, fmt.Errorf("internal error") } cert.LastDebitSerial = oldcert.LastDebitSerial cert.LastCreditSerial = oldcert.LastCreditSerial if _, ok := iv.(*document.DebitCert); ok { cert.LastDebitSerial = oldcert.Serial } else { cert.LastCreditSerial = oldcert.Serial } cert.LastCert = &oldcertid } return cert, nil } func Init(rootdir string, sk *openpgp.Entity) (err error) { db, err = store.Open(rootdir) 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("certby/draft") if err != nil { return } err = db.Ensure("certby/debit") if err != nil { return } err = db.Ensure("certby/key.issuer") if err != nil { return } err = db.Ensure("certby/key.issuer.serial") if err != nil { return } err = db.Ensure("draftby/key.issuer.nonce") if err != nil { return } err = db.Ensure("keysby/64") if err != nil { return } err = db.Ensure("keysby/32") if err != nil { return } err = setserverkey(sk) if err != nil { return } newchan = make(chan *work) delchan = make(chan *work) go dispatch() return }