epoint-client += query documents by id
[epoint] / cmd / epoint-client / epoint-client.go
index 518346c..71bbe04 100644 (file)
@@ -1,11 +1,13 @@
 package main
 
 import (
+       "bytes"
        "crypto/openpgp"
        "crypto/openpgp/armor"
        "crypto/rand"
        "epoint/document"
        "epoint/key"
+       "epoint/store"
        "fmt"
        "io"
        "log"
@@ -15,13 +17,20 @@ import (
        "strconv"
 )
 
-const usage = `usage: ./epoint-client [k|d|s|v] [args..] < [seed|document]
+// TODO: store documents, query document by id, easy submit
+
+var db *store.Conn
+
+const usage = `usage: ./epoint-client [i|h|d|q|s|v|c] [args..] < [seed|document]
 server is http://localhost:8080 by default
 
-k - make key, use seed for generation, args: [issuer] denomination
-d - make draft, use seed as signing key, args: targetid value
-s - submit a document, args: k[ey]|d[raft]|c[ert] [server]
+i - make issuer key, use seed for generation, args: denomination
+h - make holder key, use seed for generation, args: issuer
+d - make draft, use seed for signing key, args: targetid value
+q - query document, args: k|d|c id [server]
+s - submit a (key|draft|cert) document, args: k|d|c [server]
 v - verify a document (prints body of the document if ok)
+c - connect to server and get server key, args: [server]
 `
 
 func rnd(n int) (r []byte, err error) {
@@ -30,76 +39,256 @@ func rnd(n int) (r []byte, err error) {
        return
 }
 
-func k(r []byte, issuer, denom string) (err error) {
+func k(r []byte, cmd, arg string) (err error) {
        var e *openpgp.Entity
 
-       if issuer == "" {
-               e, err = key.Issuer(r, denom)
+       if cmd == "i" {
+               e, err = key.Issuer(r, arg)
        } else {
-               e, err = key.Holder(r, issuer, denom)
-       }
-       if err != nil {
-               return
+               s, err1 := db.Get("key", arg)
+               err = err1
+               if err != nil {
+                       return
+               }
+               ie, err1 := key.Parse(s)
+               err = err1
+               if err != nil {
+                       return
+               }
+               isIssuer, _, denom, err1 := key.Check(ie)
+               err = err1
+               if err != nil {
+                       return
+               }
+               if !isIssuer {
+                       err = fmt.Errorf("Not an issuer key: %s", arg)
+                       return
+               }
+               e, err = key.Holder(r, arg, denom)
        }
-       w, err := armor.Encode(os.Stdout, openpgp.PublicKeyType, nil)
        if err != nil {
                return
        }
-       // TODO: maybe Serialize should do this internally
-       for _, ident := range e.Identities {
-               err = ident.SelfSignature.SignUserId(ident.UserId.Id, e.PrimaryKey, e.PrivateKey)
+       log.Printf("generated key %s", key.Id(e))
+       d, err := db.Get("key", key.Id(e))
+       if err == nil {
+               // TODO: issuer, denom check (remove keys from store if they are not sent to server?)
+               log.Printf("key %s was found in store", key.Id(e))
+       } else {
+               out := new(bytes.Buffer)
+               w, err1 := armor.Encode(out, openpgp.PublicKeyType, nil)
+               err = err1
                if err != nil {
                        return
                }
-       }
-       for _, subkey := range e.Subkeys {
-               err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey)
+               // TODO: maybe Serialize should do this internally
+               for _, ident := range e.Identities {
+                       err = ident.SelfSignature.SignUserId(ident.UserId.Id, e.PrimaryKey, e.PrivateKey)
+                       if err != nil {
+                               return
+                       }
+               }
+               for _, subkey := range e.Subkeys {
+                       err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey)
+                       if err != nil {
+                               return
+                       }
+               }
+               err = e.Serialize(w)
+               if err != nil {
+                       return
+               }
+               err = w.Close()
+               if err != nil {
+                       return
+               }
+               _, err = out.Write([]byte{'\n'})
+               if err != nil {
+                       return
+               }
+               d = out.Bytes()
+               err = db.Insert("key", key.Id(e), d)
                if err != nil {
                        return
                }
        }
+       _, err = os.Stdout.Write(d)
+       return
+}
 
-       err = e.Serialize(w)
+func d(r []byte, target, value string) (err error) {
+       v, err := strconv.Atoi64(value)
        if err != nil {
                return
        }
-       _, err = w.Write([]byte{'\n'})
+       e, err := key.Holder(r, "", "")
        if err != nil {
                return
        }
-       err = w.Close()
-       return
-}
-
-func d(r []byte, target, value string) (err error) {
-       v, err := strconv.Atoi64(value)
+       log.Printf("drawer: %s", key.Id(e))
+       if key.Id(e) == target {
+               err = fmt.Errorf("Drawer and beneficiary are the same")
+               return
+       }
+       b, err := db.Get("key", key.Id(e))
        if err != nil {
                return
        }
-       e, err := key.Holder(r, "", "")
+       e1, err := key.Parse(b)
        if err != nil {
                return
        }
+       _, issuer, denom, err := key.Check(e1)
+       if err != nil {
+               return
+       }
+       // TODO: store server id as well?
+       b, err = db.Get("", "serverkey")
+       if err != nil {
+               return
+       }
+       sk, err := key.Parse(b)
+       if err != nil {
+               return
+       }
+       // TODO: check beneficiary (check value?)
        draft := new(document.Draft)
        draft.Drawer = key.Id(e)
        draft.Beneficiary = target
        draft.Amount = v
-       draft.Denomination = "" // TODO
-       draft.Issuer = ""       // TODO
-       draft.AuthorizedBy = "" // TODO
+       draft.Denomination = denom
+       draft.Issuer = issuer
+       draft.AuthorizedBy = key.Id(sk)
        nonce, err := rnd(10)
        if err != nil {
                return
        }
        draft.Nonce = fmt.Sprintf("%X", nonce)
-       s, _, err := document.Format(draft, e)
+       s, c, err := document.Format(draft, e)
        if err != nil {
                return
        }
+       log.Printf("draft id: %s", document.Id(c))
        _, err = os.Stdout.Write(s)
        return
 }
 
+func q(cmd, id, server string) (err error) {
+       log.Printf("document id: %s, server: %s", id, server)
+       m := map[string]string{
+               "k": "key",
+               "d": "draft",
+               "c": "cert",
+       }
+       k, ok := m[cmd]
+       if !ok {
+               err = fmt.Errorf("unknown query command: %s", cmd)
+               return
+       }
+       d, err := db.Get(k, id)
+       if err != nil {
+               if _, ok := err.(store.NotFoundError); !ok {
+                       return
+               }
+       } else {
+               _, err = os.Stdout.Write(d)
+               log.Printf("found %s in local store", id)
+               return
+       }
+       resp, err := http.Get(server+"/"+k+"/"+id)
+       if err != nil {
+               return
+       }
+       d, err = readall(resp.Body)
+       if err != nil {
+               return
+       }
+       err = resp.Body.Close()
+       if err != nil {
+               return
+       }
+       _, err = os.Stdout.Write(d)
+       if resp.StatusCode != 200 {
+               err = fmt.Errorf("request failed: %s", resp.Status)
+               return
+       }
+       if err != nil {
+               return
+       }
+       log.Printf("got %s from the server", id)
+       switch cmd {
+       case "k":
+               e, err1 := key.Parse(d)
+               err = err1
+               if err != nil {
+                       return
+               }
+               if id != key.Id(e) {
+                       err = fmt.Errorf("id mismatch, expected %s, got %s", id, key.Id(e))
+                       return
+               }
+               err = db.Set("key", id, d)
+       case "d":
+               i, s, err1 := document.Parse(d)
+               err = err1
+               if err != nil {
+                       return
+               }
+               if id != document.Id(s) {
+                       err = fmt.Errorf("id mismatch, expected %s, got %s", id, document.Id(s))
+                       return
+               }
+               draft := i.(*document.Draft)
+               b, err1 := db.Get("key", draft.Drawer)
+               err = err1
+               if err != nil {
+                       return
+               }
+               e, err1 := key.Parse(b)
+               err = err1
+               if err != nil {
+                       return
+               }
+               err = document.Verify(s, openpgp.EntityList{e})
+               if err != nil {
+                       return
+               }
+               err = db.Set("draft", id, d)
+       case "c":
+               i, s, err1 := document.Parse(d)
+               err = err1
+               if err != nil {
+                       return
+               }
+               if id != document.Id(s) {
+                       err = fmt.Errorf("id mismatch, expected %s, got %s", id, document.Id(s))
+                       return
+               }
+               cert, err1 := document.ToCert(i)
+               err = err1
+               if err != nil {
+                       return
+               }
+               // TODO: check serverkey
+               b, err1 := db.Get("key", cert.AuthorizedBy)
+               err = err1
+               if err != nil {
+                       return
+               }
+               e, err1 := key.Parse(b)
+               err = err1
+               if err != nil {
+                       return
+               }
+               err = document.Verify(s, openpgp.EntityList{e})
+               if err != nil {
+                       return
+               }
+               err = db.Set("cert", id, d)
+       }
+       return
+}
+
 func s(d []byte, cmd, server string) (err error) {
        m := map[string]string{
                "k": "key",
@@ -111,15 +300,101 @@ func s(d []byte, cmd, server string) (err error) {
                err = fmt.Errorf("unknown submit command: %s", cmd)
                return
        }
-       resp, err := http.PostForm(server, url.Values{k: {string(d)}})
+       id := ""
+       switch cmd {
+       case "k":
+               e, err1 := key.Parse(d)
+               err = err1
+               if err != nil {
+                       return
+               }
+               id = key.Id(e)
+               err = db.Set("key", id, d)
+       case "d":
+               _, s, err1 := document.Parse(d)
+               err = err1
+               if err != nil {
+                       return
+               }
+               id = document.Id(s)
+               err = db.Set("draft", id, d)
+       case "c":
+               _, s, err1 := document.Parse(d)
+               err = err1
+               if err != nil {
+                       return
+               }
+               id = document.Id(s)
+               err = db.Set("cert", id, d)
+       }
+       if err != nil {
+               return
+       }
+       log.Printf("document id: %s, server: %s", id, server)
+       resp, err := http.PostForm(server+"/submit", url.Values{k: {string(d)}})
+       if err != nil {
+               return
+       }
+       if resp.StatusCode != 200 {
+               log.Printf("request failed: %s", resp.Status)
+       }
+       // TODO: store result
+       b, err := readall(resp.Body)
+       defer resp.Body.Close()
        if err != nil {
                return
        }
-       fmt.Printf("%v", resp)
+       _, err = os.Stdout.Write(b)
+       if err != nil {
+               return
+       }
+       cert, s, err := document.Parse(b)
+       if err != nil {
+               return
+       }
+       d, err = db.Get("", "serverkey")
+       if err != nil {
+               return
+       }
+       e, err := key.Parse(d)
+       if err != nil {
+               return
+       }
+       err = document.Verify(s, openpgp.EntityList{e})
+       if err != nil {
+               return
+       }
+       log.Printf("response type: %T, response id: %s", cert, document.Id(s))
+       err = db.Set("cert", document.Id(s), b)
        return
 }
 
 func v(d []byte) (err error) {
+       // handle armored pubkey
+       if bytes.Index(d, []byte(openpgp.PublicKeyType)) >= 0 {
+               es, err1 := openpgp.ReadArmoredKeyRing(bytes.NewBuffer(d))
+               if err1 != nil {
+                       err = err1
+                       return
+               }
+               for _, e := range es {
+                       isIssuer, issuer, denom, err1 := key.Check(e)
+                       if err1 != nil {
+                               if err != nil {
+                                       log.Println(err)
+                               }
+                               err = err1
+                               continue
+                       }
+                       if isIssuer {
+                               fmt.Println("Issuer key")
+                       } else {
+                               fmt.Println("Holder key")
+                       }
+                       fmt.Printf("Issuer: %s\nDenomination: %s\nId: %s\n", issuer, denom, key.Id(e))
+               }
+               return
+       }
        _, s, err := document.Parse(d)
        if err != nil {
                return
@@ -128,16 +403,101 @@ func v(d []byte) (err error) {
        return
 }
 
-func read() []byte {
+func c(server string) (err error) {
+       resp, err := http.Get(server + "/serverkey")
+       if err != nil {
+               return
+       }
+       if resp.StatusCode != 200 {
+               log.Printf("request failed: %s\n", resp.Status)
+       }
+       b, err := readall(resp.Body)
+       if err != nil {
+               return
+       }
+       err = resp.Body.Close()
+       if err != nil {
+               return
+       }
+       e, err := key.Parse(b)
+       if err != nil {
+               return
+       }
+       log.Printf("got server key %s", key.Id(e))
+       err = db.Set("key", key.Id(e), b)
+       if err != nil {
+               return
+       }
+       err = db.Set("", "serverkey", b)
+       return
+}
+
+// TODO: commmon code with server
+func initstore(dir string) (db *store.Conn, err error) {
+       log.Printf("using root dir %s", dir)
+       db, err = store.Open(dir)
+       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")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("certby/key.serial")
+       if err != nil {
+               return
+       }
+       return
+}
+
+func storedir() string {
+       dir := os.Getenv("HOME")
+       if dir == "" {
+               dir = "/var/cache/epoint"
+       } else {
+               dir += "/.epoint-client"
+       }
+       return dir
+}
+
+func readall(r io.Reader) ([]byte, error) {
        b := make([]byte, 10000)
-       n, err := io.ReadFull(os.Stdin, b)
+       n, err := io.ReadFull(r, b)
        if err != io.ErrUnexpectedEOF {
                if err == nil {
-                       log.Fatal("too much input")
+                       err = fmt.Errorf("too much input")
                }
+               return nil, err
+       }
+       return b[:n], nil
+}
+
+func read() []byte {
+       b, err := readall(os.Stdin)
+       if err != nil {
                log.Fatal(err)
        }
-       return b[:n]
+       return b
 }
 
 func main() {
@@ -146,26 +506,34 @@ func main() {
        }
 
        var err error
+       db, err = initstore(storedir())
+       server := "http://localhost:8080"
        switch os.Args[1] {
-       case "k":
-               issuer := ""
-               denom := ""
-               if len(os.Args) == 4 {
-                       issuer = os.Args[2]
-                       denom = os.Args[3]
-               } else if len(os.Args) == 3 {
-                       denom = os.Args[2]
-               } else {
+       case "h", "i":
+               if len(os.Args) != 3 {
                        log.Fatal(usage)
                }
-               err = k(read(), issuer, denom)
+               err = k(read(), os.Args[1], os.Args[2])
        case "d":
                if len(os.Args) != 4 {
                        log.Fatal(usage)
                }
                err = d(read(), os.Args[2], os.Args[3])
+       case "q":
+               cmd := ""
+               id := ""
+               if len(os.Args) == 5 {
+                       cmd = os.Args[2]
+                       id = os.Args[3]
+                       server = os.Args[4]
+               } else if len(os.Args) == 4 {
+                       cmd = os.Args[2]
+                       id = os.Args[3]
+               } else {
+                       log.Fatal(usage)
+               }
+               err = q(cmd, id, server)
        case "s":
-               server := "http://localhost:8080"
                cmd := ""
                if len(os.Args) == 4 {
                        cmd = os.Args[2]
@@ -175,12 +543,21 @@ func main() {
                } else {
                        log.Fatal(usage)
                }
-               err = s(read(), cmd, server+"/submit")
+               err = s(read(), cmd, server)
        case "v":
                if len(os.Args) != 2 {
                        log.Fatal(usage)
                }
                err = v(read())
+       case "c":
+               if len(os.Args) == 3 {
+                       server = os.Args[2]
+               } else if len(os.Args) != 2 {
+                       log.Fatal(usage)
+               }
+               err = c(server)
+       default:
+               log.Fatal(usage)
        }
        if err != nil {
                log.Fatal(err)