X-Git-Url: http://nsz.repo.hu/git/?a=blobdiff_plain;f=pkg%2Fserver%2Fserver.go;h=8be10b2a0821577c1f50b7d448e34782644e16a5;hb=5e9fc897c292d0c51e263f6d8a4f6cd31da131cc;hp=5d9af8e2aa42658035191e9f3ef85aa7cf75ac87;hpb=60f039199482cbeffc865a259f31809aa17d9698;p=epoint diff --git a/pkg/server/server.go b/pkg/server/server.go index 5d9af8e..8be10b2 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -9,22 +9,75 @@ import ( "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 StoreSk(sk *openpgp.Entity) (err error) { - // TODO: initkey should save serverkey in db - b := new(bytes.Buffer) - err = sk.Serialize(b) +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 } - return db.Set("key", fmt.Sprintf("%X", sk.PrimaryKey.Fingerprint), b.Bytes()) + 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) { @@ -52,22 +105,27 @@ func AddKeys(d []byte) (err error) { } for _, e := range entities { // TODO: various checks.. - isIssuer, issuer, denom, err1 := key.CheckEntity(e) + // TODO: collect errors instead of aborting addkeys + isIssuer, issuer, denom, err1 := key.Check(e) err = err1 if err != nil { - // TODO.. - continue + return } if !isIssuer { - es, err := GetKeys(issuer) + es, err1 := GetKeys(issuer) + err = err1 if err != nil { - // TODO.. - continue + return } - ok, _, den, err := key.CheckEntity(es[0]) - if !ok || err != nil || den != denom { - // TODO.. - continue + 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) @@ -75,8 +133,8 @@ func AddKeys(d []byte) (err error) { if err != nil { return } - fpr := fmt.Sprintf("%X", e.PrimaryKey.Fingerprint) - err = db.Set("key", fpr, b.Bytes()) + fpr := key.Id(e) + err = db.Insert("key", fpr, b.Bytes()) if err != nil { return } @@ -92,141 +150,306 @@ func AddKeys(d []byte) (err error) { return } -func CertByDraft(draftid string) (d []byte, err error) { - certid, err := db.Get("certby/draft", draftid) +// Get cert through the named store +func GetCert(name, id string) (d []byte, err error) { + certid, err := db.Get(name, id) if err != nil { - // TODO: we have the draft but the cert is not ready + // ok if notfound return } d, err = db.Get("cert", string(certid)) if err != nil { - // shouldn't happen, cert is not available + // internal error: cert is not available return } return } -func CertByDebitCert(debitid string) (d []byte, err error) { - creditid, err := db.Get("certby/debit", debitid) +func EvalDraft(d []byte) (c []byte, err error) { + iv, signed, err := document.Parse(d) if err != nil { - // TODO: we have the debit cert but the credit cert is not ready return } - d, err = db.Get("cert", string(creditid)) + 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 { - // shouldn't happen, cert is not available return } - 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) } -// parse clear signed draft and verify it -func ParseDraft(d []byte) (draft *document.Draft, draftid string, err error) { +func EvalDebitCert(d []byte) (c []byte, err error) { iv, signed, err := document.Parse(d) if err != nil { return } - draft, ok := iv.(*document.Draft) + cert, ok := iv.(*document.DebitCert) if !ok { - err = fmt.Errorf("ParseDraft: expected a draft docuent") + err = fmt.Errorf("ParseDebitCert: expected a debit docuent") return } - draftid = document.Id(signed) - k, err := db.Get("key", draft.Drawer) + 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 } - kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k)) + 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 { - // internal error: pubkey cannot be parsed return } - err = document.Verify(signed, kr) + err = db.Set("draft", w.docid, w.in) if err != nil { return } - _, issuer, denom, err := key.CheckEntity(kr[0]) + err = db.Set("draftby/key.issuer.nonce", nonce, []byte(w.docid)) if err != nil { return } - k, err = db.Get("key", draft.Beneficiary) + cert, err := newDebitCert(w) if err != nil { + // probably client error + db.End(w.docid) return } - kr, err = openpgp.ReadKeyRing(bytes.NewBuffer(k)) + c, signed, err := document.Format(cert, serverkey) if err != nil { - // internal error: pubkey cannot be parsed return } - _, issuer2, denom2, err := key.CheckEntity(kr[0]) + certid := document.Id(signed) + err = db.Set("cert", certid, c) if err != nil { + // internal error return } - if draft.Issuer != issuer || - draft.Issuer != issuer2 || - draft.Denomination != denom || - draft.Denomination != denom2 { - err = fmt.Errorf("Issuer or denomination mismatch") + err = db.Set("certby/draft", w.docid, []byte(certid)) + if err != nil { + // internal error 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) + // 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 ParseDebitCert(d []byte) (cert *document.DebitCert, certid string, err error) { - iv, signed, err := document.Parse(d) +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 } - cert, ok := iv.(*document.DebitCert) - if !ok { - err = fmt.Errorf("ParseDebitCert: expected a debit docuent") + err = db.Set("cert", w.docid, w.in) + if err != nil { return } - - k, err := db.Get("key", cert.AuthorizedBy) + // TODO: check pubkey etc + cert, err := newCreditCert(w) if err != nil { + // internal error return } - // TODO: keep our key at hand - kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k)) + c, signed, err := document.Format(cert, serverkey) if err != nil { - // internal error: pubkey cannot be parsed + // internal error return } - // must clean up to make sure the hash is ok - err = document.Verify(signed, kr) + certid := document.Id(signed) + err = db.Set("cert", certid, c) if err != nil { + // internal error return } - - certid = document.Id(signed) + 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 NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, error) { +func isIssuer(c *document.Cert) bool { + return c.Issuer == c.Holder +} + +func newDebitCert(w *work) (*document.DebitCert, error) { cert := new(document.DebitCert) - cert.Holder = draft.Drawer - cert.Date = time.Seconds() + cert.Holder = w.draft.Drawer + cert.Date = time.Now().Unix() cert.Denomination = "epoint" - cert.Issuer = draft.Issuer - cert.AuthorizedBy = draft.AuthorizedBy - cert.Difference = -draft.Amount - cert.Draft = draftid - cert.Beneficiary = draft.Beneficiary + 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", draft.Drawer) + oid, err := db.Get("certby/key.issuer", w.account) 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") + 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 @@ -242,6 +465,7 @@ func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, e // 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 { @@ -254,12 +478,12 @@ func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, e 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) + 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 { + if _, ok := iv.(*document.DebitCert); ok { cert.LastDebitSerial = oldcert.Serial } else { cert.LastCreditSerial = oldcert.Serial @@ -269,23 +493,27 @@ func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, e return cert, nil } -func NewCreditCert(draftid string, draft *document.Draft, dcertid string, dcert *document.DebitCert) (*document.CreditCert, error) { +func newCreditCert(w *work) (*document.CreditCert, error) { cert := new(document.CreditCert) // TODO: get from old cert instead? - cert.Holder = dcert.Beneficiary - cert.Date = time.Seconds() + cert.Holder = w.debit.Beneficiary + cert.Date = time.Now().Unix() // 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 + 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", dcert.Beneficiary) + 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 @@ -313,9 +541,13 @@ func NewCreditCert(draftid string, draft *document.Draft, dcertid string, dcert 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 { + if _, ok := iv.(*document.DebitCert); ok { cert.LastDebitSerial = oldcert.Serial } else { cert.LastCreditSerial = oldcert.Serial @@ -325,134 +557,7 @@ func NewCreditCert(draftid string, draft *document.Draft, dcertid string, dcert return cert, nil } -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 - // TODO: certby/draft might not be ready even if draft is there - return CertByDraft(draftid) - } - // if draft is ok we save it - err = db.Set("draft", draftid, d) - if err != nil { - // internal error - return - } - // TODO: db.Insert: fails if key exists - s := fmt.Sprintf("%s.%s", draft.Drawer, draft.Nonce) - _, err = db.Get("draftby/key.nonce", s) - if err == nil { - err = fmt.Errorf("draft nonce is not unique") - return - } - err = db.Set("draftby/key.nonce", s, 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("certby/draft", draftid, []byte(certid)) - if err != nil { - // internal error - return - } - err = db.Set("certby/key", cert.Holder, []byte(certid)) - if err != nil { - // internal error - return - } - // TODO: append? - err = db.Set("certby/key.serial", 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("certby/debit", dcertid, []byte(certid)) - if err != nil { - // internal error - return - } - err = db.Set("certby/key", cert.Holder, []byte(certid)) - if err != nil { - // internal error - return - } - // TODO: append? - err = db.Set("certby/key.serial", fmt.Sprintf("%s.%09d", cert.Holder, cert.Serial), []byte(certid)) - if err != nil { - // internal error - return - } - return -} - -func Init(rootdir string) (err error) { +func Init(rootdir string, sk *openpgp.Entity) (err error) { db, err = store.Open(rootdir) if err != nil { return @@ -477,15 +582,15 @@ func Init(rootdir string) (err error) { if err != nil { return } - err = db.Ensure("certby/key") + err = db.Ensure("certby/key.issuer") if err != nil { return } - err = db.Ensure("certby/key.serial") + err = db.Ensure("certby/key.issuer.serial") if err != nil { return } - err = db.Ensure("draftby/key.nonce") + err = db.Ensure("draftby/key.issuer.nonce") if err != nil { return } @@ -497,5 +602,12 @@ func Init(rootdir string) (err error) { if err != nil { return } + err = setserverkey(sk) + if err != nil { + return + } + newchan = make(chan *work) + delchan = make(chan *work) + go dispatch() return }