package main
+import (
+ "bytes"
+ "crypto/openpgp"
+ "crypto/openpgp/armor"
+ "crypto/rand"
+ "epoint/document"
+ "epoint/key"
+ "epoint/store"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "net/url"
+ "os"
+ "strconv"
+)
+
+// TODO: store documents, query document by id, easy submit
+
+var db *store.Conn
+
+const usage = `usage: ./epoint-client [i|h|d|r|q|s|v|c] [args..] < [seed|document]
+server is http://localhost:8080 by default
+
+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
+r - sign raw document, use seed for signing key, args: document
+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) {
+ r = make([]byte, n)
+ _, err = io.ReadFull(rand.Reader, r)
+ return
+}
+
+func k(r []byte, cmd, arg string) (err error) {
+ var e *openpgp.Entity
+
+ if cmd == "i" {
+ e, err = key.Issuer(r, arg)
+ } else {
+ 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)
+ }
+ if err != nil {
+ return
+ }
+ 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
+ }
+ // 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
+}
+
+func d(r []byte, target, value string) (err error) {
+ v, err := strconv.ParseInt(value, 10, 64)
+ if err != nil {
+ return
+ }
+ e, err := key.Holder(r, "", "")
+ if err != nil {
+ return
+ }
+ 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
+ }
+ 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 = denom
+ draft.Issuer = issuer
+ draft.AuthorizedBy = key.Id(sk)
+ nonce, err := rnd(10)
+ if err != nil {
+ return
+ }
+ draft.Nonce = fmt.Sprintf("%X", nonce)
+ 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
+}
+
+// TODO: document.Sign does not handle dash escape
+func raw(r []byte, name string) (err error) {
+ e, err := key.Holder(r, "", "")
+ if err != nil {
+ return
+ }
+ log.Printf("signer: %s", key.Id(e))
+ f, err := os.Open(name)
+ if err != nil {
+ return
+ }
+ doc, err := readall(f)
+ if err != nil {
+ return
+ }
+ d, err := document.Sign(doc, e)
+ if err != nil {
+ return
+ }
+ s, err := document.FormatSigned(d)
+ if err != nil {
+ return
+ }
+ _, 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",
+ "d": "draft",
+ "c": "debit",
+ }
+ k, ok := m[cmd]
+ if !ok {
+ err = fmt.Errorf("unknown submit command: %s", cmd)
+ return
+ }
+ 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
+ }
+ _, 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
+ }
+ _, err = os.Stdout.Write(s.Body)
+ return
+}
+
+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
+}
+
+// read all but at most 1M from r
+func readall(r io.Reader) ([]byte, error) {
+ b := make([]byte, 1<<20)
+ n, err := io.ReadFull(r, b)
+ if err != io.ErrUnexpectedEOF {
+ if err == nil {
+ 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
+}
+
func main() {
+ if len(os.Args) < 2 {
+ log.Fatal(usage)
+ }
+
+ var err error
+ db, err = initstore(storedir())
+ server := "http://localhost:8080"
+ switch os.Args[1] {
+ case "h", "i":
+ if len(os.Args) != 3 {
+ log.Fatal(usage)
+ }
+ 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 "r":
+ if len(os.Args) != 3 {
+ log.Fatal(usage)
+ }
+ err = raw(read(), os.Args[2])
+ 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":
+ cmd := ""
+ if len(os.Args) == 4 {
+ cmd = os.Args[2]
+ server = os.Args[3]
+ } else if len(os.Args) == 3 {
+ cmd = os.Args[2]
+ } else {
+ log.Fatal(usage)
+ }
+ 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)
+ }
}