diff --git a/crypto/eecdh.go b/crypto/eecdh.go index 561e5e4..31a3bc8 100644 --- a/crypto/eecdh.go +++ b/crypto/eecdh.go @@ -10,6 +10,7 @@ import( "golang.org/x/crypto/pbkdf2" "errors" "math/big" + "fmt" ) const( @@ -27,10 +28,9 @@ var( ) type EncryptedMessage struct { - Ax []byte // Elliptic-Curve X-value of the message sender - Ay []byte // Elliptic-Curve Y-value of the message sender - Bx []byte // Elliptic-Curve X-value of the message receiver - By []byte // Elliptic-Curve Y-value of the message receiver + Sid int // The index of this sender's key to use in this diffie-hellman + Rid int // The index of the receiver's key to use in this diffie-hellman + Nxt []byte // Elliptic-Curve public data for the next message (encrypted) IV []byte // AES IV used to encrypt the message and HMAC key Msg []byte // AES encrypted message data Key []byte // AES encrypted HMAC key @@ -42,34 +42,45 @@ func DeriveKey(mother []byte, keysize int) []byte { return pbkdf2.Key(mother, nil, 4096, keysize, secureHash) } +func ECDH(priv []byte, x, y *big.Int) []byte { + // Create shared secret xp from peer's public key and our private key + xp, _ := Curve.ScalarMult(x, y, priv) + + // Derive an AES key from our shared secret + return DeriveKey(xp.Bytes(), aesKeySize) +} + // Encrypt encrypts clearText using a shared secret acquired through an // elliptic-curve diffie-hellman key exchange. // // Your private diffie-hellman information, priv, is used with the peer's // public diffie-hellman information (bx, by), to create a shared AES session // key to encrypt clearText with. Returns an EncryptedMessage and an error. -func EncryptMessage(clearText, priv []byte, ax, ay, bx, by *big.Int) (msg *EncryptedMessage, err error) { - // Create shared secret xp from peer's public key and our private key - xp, _ := Curve.ScalarMult(bx, by, priv) - - // Derive an AES key from our shared secret - aesKey := DeriveKey(xp.Bytes(), aesKeySize) - +func EncryptMessage(clearText, aesKey, nxt []byte, sid, rid int) (msg *EncryptedMessage, err error) { // Create a random HMAC key hmacKey := make([]byte, hmacKeySize) if _, err := rand.Read(hmacKey); err != nil { return nil, err } + fmt.Println(nxt) + // Add PKCS7 padding to clearText paddedClearText, err := PKCS7Pad(clearText, aes.BlockSize) if err != nil { return nil, err } + // Add PKCS7 padding to next key + nxt, err = PKCS7Pad(nxt, aes.BlockSize) + if err != nil { + return nil, err + } + // Create buffers for ciphertexts cipherText := make([]byte, len(paddedClearText)) encryptedKey := make([]byte, len(hmacKey)) + encryptedNxt := make([]byte, len(nxt)) // Create AES block cipher aesCipher, err := aes.NewCipher(aesKey) @@ -91,6 +102,9 @@ func EncryptMessage(clearText, priv []byte, ax, ay, bx, by *big.Int) (msg *Encry cbc = cipher.NewCBCEncrypter(aesCipher, iv) cbc.CryptBlocks(encryptedKey, hmacKey) + // Encrypt nxt key with CBC block encrypter + cbc = cipher.NewCBCEncrypter(aesCipher, iv) + cbc.CryptBlocks(encryptedNxt, nxt) // Generate MAC tag for data mac := hmac.New(secureHash, hmacKey) @@ -98,10 +112,9 @@ func EncryptMessage(clearText, priv []byte, ax, ay, bx, by *big.Int) (msg *Encry tag := mac.Sum(nil) msg = &EncryptedMessage{ - Ax: ax.Bytes(), - Ay: ay.Bytes(), - Bx: bx.Bytes(), - By: by.Bytes(), + Sid: sid, + Rid: rid, + Nxt: encryptedNxt, IV: iv, Msg: cipherText, Tag: tag, @@ -111,28 +124,11 @@ func EncryptMessage(clearText, priv []byte, ax, ay, bx, by *big.Int) (msg *Encry return msg, err } -func (message *EncryptedMessage) Decrypt(priv []byte, sender bool) ([]byte, error) { - x := new(big.Int) - y := new(big.Int) - - if sender { - x.SetBytes(message.Bx) - y.SetBytes(message.By) - } else { - x.SetBytes(message.Ax) - y.SetBytes(message.Ay) - } - - // Create shared secret xp from peer's public key and our private key - xp, _ := Curve.ScalarMult(x, y, priv) - - // Derive an AES key from our shared secret - aesKey := DeriveKey(xp.Bytes(), aesKeySize) - +func (message *EncryptedMessage) Decrypt(aesKey []byte) (clearText, nextKey []byte, err error) { // Create AES block cipher aesCipher, err := aes.NewCipher(aesKey) if err != nil { - return nil, err + return } // Decrypt HMAC Key @@ -141,7 +137,8 @@ func (message *EncryptedMessage) Decrypt(priv []byte, sender bool) ([]byte, erro // Compare MAC tags if !CheckMAC(message.Msg, message.Tag, message.Key) { - return nil, ErrUnexpectedMAC + err = ErrUnexpectedMAC + return } // Decrypt and unpad the payload @@ -149,10 +146,20 @@ func (message *EncryptedMessage) Decrypt(priv []byte, sender bool) ([]byte, erro cbc.CryptBlocks(message.Msg, message.Msg) msg, err := PKCS7Unpad(message.Msg, aes.BlockSize) if err != nil { - return nil, err + return } - return msg, err + // Decrypt and unpad the next key + cbc = cipher.NewCBCDecrypter(aesCipher, message.IV) + cbc.CryptBlocks(message.Nxt, message.Nxt) + nxt, err := PKCS7Unpad(message.Nxt, aes.BlockSize) + if err != nil { + return + } + + fmt.Println(nxt) + + return msg, nxt, err } func CheckMAC(message, messageMAC, key []byte) bool { diff --git a/db/db.go b/db/db.go index a67aac3..ad328e4 100644 --- a/db/db.go +++ b/db/db.go @@ -22,18 +22,33 @@ func init() { } func InitTables() error { - _, err := db.Exec(`CREATE TABLE IF NOT EXISTS dh( - id INT PRIMARY KEY AUTO_INCREMENT, + _, err := db.Exec(`CREATE TABLE IF NOT EXISTS pubkeys( + id INT, owner VARCHAR(16), - pub BLOB, - priv BLOB - );`) + peer VARCHAR(16), + pubkey BLOB, + primary key (id, owner, peer));`) + if err != nil { + return err + } + + _, err = db.Exec(`CREATE TABLE IF NOT EXISTS privkeys( + id INT, + owner VARCHAR(16), + peer VARCHAR(16), + privkey BLOB, + primary key (id, owner, peer));`) return err } func ResetTables() error { - _, err := db.Exec("DROP TABLE IF EXISTS dh") + _, err := db.Exec("DROP TABLE IF EXISTS pubkeys") + if err != nil { + return err + } + + _, err = db.Exec("DROP TABLE IF EXISTS privkeys") if err != nil { return err } @@ -41,32 +56,62 @@ func ResetTables() error { return InitTables() } -func LookupPubKey(owner string) (pub []byte, err error) { +func LookupPubKey(owner, peer string, id int) (pub []byte, err error) { row := db.QueryRow( - `SELECT pub - FROM dh - WHERE owner = ? - ORDER BY id DESC`, - owner) + `SELECT pubkey + FROM pubkeys + WHERE owner = ? AND peer = ? AND id = ?`, + owner, peer, id) err = row.Scan(&pub) return } -func LookupPrivKey(owner string, pub []byte) (priv []byte, err error) { +func LatestPubKey(owner, peer string) (id int) { row := db.QueryRow( - `SELECT priv - FROM dh - WHERE owner = ? AND pub = ? AND priv IS NOT NULL`, - owner, pub) + `SELECT id + FROM pubkeys + WHERE owner = ? AND peer = ? + ORDER BY id DESC`, + owner, peer) + err := row.Scan(&id) + if err != nil { + return 0 + } + return id +} + + +func LookupPrivKey(owner, peer string, id int) (priv []byte, err error) { + row := db.QueryRow( + `SELECT privkey + FROM privkeys + WHERE owner = ? AND peer = ? AND id = ? + ORDER BY id DESC`, + owner, peer, id) err = row.Scan(&priv) return } -func UploadKey(owner string, pub, priv []byte) error { - if priv != nil { - _, err := db.Exec(`INSERT INTO dh (owner, pub, priv) VALUES (?, ?, ?)`, owner, pub, priv) - return err +func LatestPrivKey(owner, peer string) (id int) { + row := db.QueryRow( + `SELECT id + FROM privkeys + WHERE owner = ? AND peer = ? + ORDER BY id DESC`, + owner, peer) + err := row.Scan(&id) + if err != nil { + return 0 } - _, err := db.Exec(`INSERT INTO dh (owner, pub) VALUES (?, ?)`, owner, pub) + return id +} + +func UploadPubKey(owner, peer string, pubkey []byte, id int) error { + _, err := db.Exec(`INSERT INTO pubkeys (id, owner, peer, pubkey) VALUES (?, ?, ?, ?)`, id, owner, peer, pubkey) + return err +} + +func UploadPrivKey(owner, peer string, privkey []byte, id int) error { + _, err := db.Exec(`INSERT INTO privkeys (id, owner, peer, privkey) VALUES (?, ?, ?, ?)`, id, owner, peer, privkey) return err } diff --git a/main.go b/main.go index ea59fd7..221123d 100644 --- a/main.go +++ b/main.go @@ -55,11 +55,12 @@ func main() { ui.Window.SetTitle("Quicksilver - Chatting with " + peer) var( + s int + r int + myPriv []byte myX *big.Int myY *big.Int - peerX *big.Int - peerY *big.Int ) myPriv, myX, myY, err = elliptic.GenerateKey(crypto.Curve, rand.Reader) @@ -68,8 +69,11 @@ func main() { } myPub := elliptic.Marshal(crypto.Curve, myX, myY) - peerKey, err := db.LookupPubKey(peer) + _, err = db.LookupPubKey(peer, user, 0) if err == sql.ErrNoRows { + s = 0 + r = 0 + outfile := os.Getenv("HOME") + "/" + user + ".ecdh" err = ioutil.WriteFile(outfile, myPub, 0666) if err != nil { @@ -91,12 +95,12 @@ func main() { file := fc.GetFilename() fc.Destroy() - peerKey, err = ioutil.ReadFile(file) + peerKey, err := ioutil.ReadFile(file) if err != nil { log.Fatal(err) } - err = db.UploadKey(peer, peerKey, nil) + err = db.UploadPubKey(peer, user, peerKey, r) if err != nil { log.Fatal(err) } @@ -107,54 +111,66 @@ func main() { log.Fatal(err) } - err = db.UploadKey(user, myPub, myPriv) + s = db.LatestPrivKey(user, peer) + + err = db.UploadPrivKey(user, peer, myPriv, s) if err != nil { log.Fatal(err) } - peerX, peerY = elliptic.Unmarshal(crypto.Curve, peerKey) - if peerX == nil { - log.Fatal("Invalid key data") - } - mux := new(sync.Mutex) - go MessagePoll(jwt, user, peer, peerX, peerY, mux, ui) + go MessagePoll(jwt, user, peer, &s, &r, mux, ui) ui.Callback = func(msg string) { - mux.Lock() - c, err := crypto.EncryptMessage([]byte(msg), myPriv, myX, myY, peerX, peerY) - mux.Unlock() + nxtPriv, nxtX, nxtY, err := elliptic.GenerateKey(crypto.Curve, rand.Reader) + if err != nil { + log.Fatal(err) + } + nxtPub := elliptic.Marshal(crypto.Curve, nxtX, nxtY) + err = db.UploadPrivKey(user, peer, nxtPriv, s + 1) + if err != nil { + log.Fatal(err) + } + peerKey, err := db.LookupPubKey(peer, user, r) if err != nil { - log.Println(err) + log.Fatal(err) + } + peerX, peerY := elliptic.Unmarshal(crypto.Curve, peerKey) + if peerX == nil { + log.Fatal("Invalid key data") } - payload, err := json.Marshal(c) + sessionKey := crypto.ECDH(myPriv, peerX, peerY) if err != nil { - log.Println(err) + log.Fatal(err) } - err = api.MessageSend(jwt, peer, string(payload)) + mux.Lock() + c, err := crypto.EncryptMessage([]byte(msg), sessionKey, nxtPub, s, r) + s += 1 + mux.Unlock() + myPriv = nxtPriv + if err != nil { log.Println(err) } - myPriv, myX, myY, err = elliptic.GenerateKey(crypto.Curve, rand.Reader) + payload, err := json.Marshal(c) if err != nil { - log.Fatal(err) + log.Println(err) } - myPub := elliptic.Marshal(crypto.Curve, myX, myY) - err = db.UploadKey(user, myPub, myPriv) + err = api.MessageSend(jwt, peer, string(payload)) if err != nil { - log.Fatal(err) + log.Println(err) } } gtk.Main() } -func MessagePoll(jwt []byte, user, peer string, px, py *big.Int, mux *sync.Mutex, ui *gui.UI) { +func MessagePoll(jwt []byte, user, peer string, s, r *int, mux *sync.Mutex, ui *gui.UI) { timestamp := "" for { messages, err := api.MessageFetch(jwt, peer, timestamp) @@ -167,41 +183,51 @@ func MessagePoll(jwt []byte, user, peer string, px, py *big.Int, mux *sync.Mutex sender := user == message.Username - x := new(big.Int) - y := new(big.Int) - + var sessionKey []byte if sender { - x.SetBytes(message.Message.Ax) - y.SetBytes(message.Message.Ay) - - mux.Lock() - px.SetBytes(message.Message.Bx) - py.SetBytes(message.Message.By) - mux.Unlock() + priv, err := db.LookupPrivKey(user, peer, message.Message.Sid) + if err != nil { + log.Fatal(err) + } + + pub, err := db.LookupPubKey(peer, user, message.Message.Rid) + if err != nil { + log.Fatal(err) + } + + x, y := elliptic.Unmarshal(crypto.Curve, pub) + if x == nil { + log.Fatal("Invalid public key.") + } + + sessionKey = crypto.ECDH(priv, x, y) } else { - x.SetBytes(message.Message.Bx) - y.SetBytes(message.Message.By) - - mux.Lock() - px.SetBytes(message.Message.Ax) - py.SetBytes(message.Message.Ay) - mux.Unlock() + priv, err := db.LookupPrivKey(user, peer, message.Message.Rid) + if err != nil { + log.Fatal(err) + } + + pub, err := db.LookupPubKey(peer, user, message.Message.Sid) + if err != nil { + log.Fatal(err) + } + + x, y := elliptic.Unmarshal(crypto.Curve, pub) + if x == nil { + log.Fatal("Invalid public key.") + } + + sessionKey = crypto.ECDH(priv, x, y) } - pubKey := elliptic.Marshal(crypto.Curve, x, y) - - if pubKey == nil { - log.Println("booty") - } - - privKey, err := db.LookupPrivKey(user, pubKey) + clearText, nextKey, err := message.Message.Decrypt(sessionKey) if err != nil { log.Println(err) } - clearText, err := message.Message.Decrypt(privKey, sender) - if err != nil { - log.Println(err) + if !sender { + *r += 1 + db.UploadPubKey(peer, user, nextKey, *r) } output := map[string]string{ @@ -209,6 +235,7 @@ func MessagePoll(jwt []byte, user, peer string, px, py *big.Int, mux *sync.Mutex "Timestamp": message.Timestamp, "Message": string(clearText), } + glib.IdleAdd(ui.ShowMessage, output) } }