diff --git a/client.go b/client.go index 1b77f35..b16befe 100644 --- a/client.go +++ b/client.go @@ -282,7 +282,9 @@ func (c *Client) exec(line string) { case "notes/list": c.listNotes(parts[1:]) case "keys/get": - c.getKey(parts[1:]) + c.fetchKey(parts[1:]) + case "msg/send": + c.sendMessage(parts[1:]) default: c.err("unrecognized client command: %s", parts[0]) } @@ -386,7 +388,7 @@ func (c *Client) encryptNote(title string, message []rune) (*EncryptedNote, erro // key functions // ------------------------------------------------------------------------------ -func (c *Client) getKey(args []string) { +func (c *Client) fetchKey(args []string) { if len(args) != 1 { c.err("keys/get takes exactly one arg") return @@ -408,18 +410,93 @@ func (c *Client) saveKey(nick string, key rsa.PublicKey) { c.keyStore[nick] = key } +func (c *Client) getKey(nick string) (*rsa.PublicKey, error) { + if key, ok := c.keyStore[nick]; ok { + return &key, nil + } + c.fetchKey([]string{nick}) + if key, ok := c.keyStore[nick]; ok { + return &key, nil + } + return nil, fmt.Errorf("no such key") +} + func (c *Client) handleKeyResponse(body json.RawMessage) error { - c.info(string(body)) + // c.info(string(body)) var res KeyResponse if err := json.Unmarshal(body, &res); err != nil { c.err(err.Error()) return err } - c.info("%v", res) + // c.info("%v", res) c.saveKey(res.Nick, res.Key) return nil } +// ------------------------------------------------------------------------------ +// message functions +// ------------------------------------------------------------------------------ + +func (c *Client) sendMessage(args []string) { + if len(args) != 1 { + c.err("send message requires exactly 1 arg, saw %d", len(args)) + return + } + to := args[0] + + c.info("fetching key...") + pkey, err := c.getKey(to) + if err != nil { + c.err("%v", err) + return + } + c.info("ok we have a key") + + text, err := c.readTextBlock() + if err != nil { + c.err("%v", err) + return + } + + aesKey, err := c.aesKey() + if err != nil { + c.err("couldn't create an aes key: %v", err) + return + } + + ctext, err := c.aesEncrypt(aesKey, []byte(string(text))) + if err != nil { + c.err("couldn't aes encrypt message text: %v", err) + return + } + + cnick, err := c.aesEncrypt(aesKey, []byte(c.nick)) + if err != nil { + c.err("couldn't aes encrypt nick: %v", err) + return + } + + ckey, err := rsa.EncryptPKCS1v15(rand.Reader, pkey, aesKey) + if err != nil { + c.err("couldn't rsa encrypt aes key: %v", err) + return + } + + m := Message{ + Key: ckey, + From: cnick, + To: to, + Text: ctext, + } + + res, err := c.sendRequest(m) + if err != nil { + c.err("%v", err) + return + } + c.info("%v", <-res) +} + func (c *Client) readTextBlock() ([]rune, error) { // god dammit what have i gotten myself into msg := make([]rune, 0, 400) diff --git a/db.go b/db.go index 565d048..bf54b9a 100644 --- a/db.go +++ b/db.go @@ -5,6 +5,9 @@ import ( "encoding/json" "fmt" "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/opt" + "github.com/syndtr/goleveldb/leveldb/util" + "strings" "sync" ) @@ -30,13 +33,34 @@ func (db *userdb) getPublicKey() (*rsa.PublicKey, error) { return &key, nil } -func getUserDB(nick string) (*userdb, error) { +func (db *userdb) nextKey(prefix string) (string, error) { + r := util.BytesPrefix([]byte(prefix)) + it := db.NewIterator(r, nil) + defer it.Release() + + id := 0 + if it.Last() { + key := it.Key() + id_s := strings.TrimPrefix(string(key), prefix) + lastId, err := decodeInt(id_s) + if err != nil { + return "0", fmt.Errorf("error getting note id: %v", err) + } + id = lastId + 1 + } + return fmt.Sprintf("%s%s", prefix, encodeInt(id)), nil +} + +func getUserDB(nick string, create bool) (*userdb, error) { if db, ok := openDBs[nick]; ok { return &db, nil } + opts := &opt.Options{ + ErrorIfMissing: create, + } path := fmt.Sprintf("./%s.db", nick) - conn, err := leveldb.OpenFile(path, nil) + conn, err := leveldb.OpenFile(path, opts) if err != nil { return nil, fmt.Errorf("unable to open db file at %s: %v", path, err) } @@ -51,7 +75,7 @@ func getUserDB(nick string) (*userdb, error) { } func getUserKey(nick string) (*rsa.PublicKey, error) { - db, err := getUserDB(nick) + db, err := getUserDB(nick, false) if err != nil { return nil, err } diff --git a/message b/message new file mode 100644 index 0000000..bc8671c --- /dev/null +++ b/message @@ -0,0 +1 @@ +This is Alice. This is Alice's secret message for herself. diff --git a/request.go b/request.go index 72f3eca..0e5f675 100644 --- a/request.go +++ b/request.go @@ -13,6 +13,12 @@ type Envelope struct { Body json.RawMessage } +type Bool bool + +func (b Bool) Kind() string { + return "bool" +} + type request interface { Kind() string } diff --git a/server.go b/server.go index c86fec7..b78f64f 100644 --- a/server.go +++ b/server.go @@ -52,6 +52,8 @@ func (s *serverConnection) handleRequest(request Envelope) error { return s.handleListNotesRequest(request.Id, request.Body) case "key": return s.handleKeyRequest(request.Id, request.Body) + case "message": + return s.handleMessageRequest(request.Id, request.Body) default: return fmt.Errorf("no such request type: %v", request.Kind) } @@ -208,8 +210,31 @@ func (s *serverConnection) handleKeyRequest(requestId int, body json.RawMessage) return s.sendResponse(requestId, res) } +func (s *serverConnection) handleMessageRequest(requestId int, body json.RawMessage) error { + var req Message + if err := json.Unmarshal(body, &req); err != nil { + error_log.Printf("unable to read message request: %v", err) + return err + } + + db, err := getUserDB(req.To, false) + if err != nil { + return err + } + + k, err := db.nextKey("messages/") + if err != nil { + return fmt.Errorf("unable to save message: %v", err) + } + + if err := db.Put([]byte(k), body, nil); err != nil { + return fmt.Errorf("unable to save message: %v", err) + } + return s.sendResponse(requestId, Bool(true)) +} + func (s *serverConnection) openDB() error { - db, err := getUserDB(s.nick) + db, err := getUserDB(s.nick, true) if err != nil { return err }