You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

344 lines
10 KiB

  1. package conn
  2. import (
  3. "bytes"
  4. crand "crypto/rand"
  5. "crypto/sha256"
  6. "encoding/binary"
  7. "errors"
  8. "io"
  9. "net"
  10. "time"
  11. // forked to github.com/tendermint/crypto
  12. "golang.org/x/crypto/chacha20poly1305"
  13. "golang.org/x/crypto/curve25519"
  14. "golang.org/x/crypto/nacl/box"
  15. "github.com/tendermint/tendermint/crypto"
  16. cmn "github.com/tendermint/tendermint/libs/common"
  17. "golang.org/x/crypto/hkdf"
  18. )
  19. // 4 + 1024 == 1028 total frame size
  20. const dataLenSize = 4
  21. const dataMaxSize = 1024
  22. const totalFrameSize = dataMaxSize + dataLenSize
  23. const aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag
  24. const aeadKeySize = chacha20poly1305.KeySize
  25. const aeadNonceSize = chacha20poly1305.NonceSize
  26. // SecretConnection implements net.conn.
  27. // It is an implementation of the STS protocol.
  28. // Note we do not (yet) assume that a remote peer's pubkey
  29. // is known ahead of time, and thus we are technically
  30. // still vulnerable to MITM. (TODO!)
  31. // See docs/sts-final.pdf for more info
  32. type SecretConnection struct {
  33. conn io.ReadWriteCloser
  34. recvBuffer []byte
  35. recvNonce *[aeadNonceSize]byte
  36. sendNonce *[aeadNonceSize]byte
  37. recvSecret *[aeadKeySize]byte
  38. sendSecret *[aeadKeySize]byte
  39. remPubKey crypto.PubKey
  40. }
  41. // MakeSecretConnection performs handshake and returns a new authenticated
  42. // SecretConnection.
  43. // Returns nil if there is an error in handshake.
  44. // Caller should call conn.Close()
  45. // See docs/sts-final.pdf for more information.
  46. func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*SecretConnection, error) {
  47. locPubKey := locPrivKey.PubKey()
  48. // Generate ephemeral keys for perfect forward secrecy.
  49. locEphPub, locEphPriv := genEphKeys()
  50. // Write local ephemeral pubkey and receive one too.
  51. // NOTE: every 32-byte string is accepted as a Curve25519 public key
  52. // (see DJB's Curve25519 paper: http://cr.yp.to/ecdh/curve25519-20060209.pdf)
  53. remEphPub, err := shareEphPubKey(conn, locEphPub)
  54. if err != nil {
  55. return nil, err
  56. }
  57. // Sort by lexical order.
  58. loEphPub, _ := sort32(locEphPub, remEphPub)
  59. // Check if the local ephemeral public key
  60. // was the least, lexicographically sorted.
  61. locIsLeast := bytes.Equal(locEphPub[:], loEphPub[:])
  62. // Compute common diffie hellman secret using X25519.
  63. dhSecret := computeDHSecret(remEphPub, locEphPriv)
  64. // generate the secret used for receiving, sending, challenge via hkdf-sha2 on dhSecret
  65. recvSecret, sendSecret, challenge := deriveSecretAndChallenge(dhSecret, locIsLeast)
  66. // Construct SecretConnection.
  67. sc := &SecretConnection{
  68. conn: conn,
  69. recvBuffer: nil,
  70. recvNonce: new([aeadNonceSize]byte),
  71. sendNonce: new([aeadNonceSize]byte),
  72. recvSecret: recvSecret,
  73. sendSecret: sendSecret,
  74. }
  75. // Sign the challenge bytes for authentication.
  76. locSignature := signChallenge(challenge, locPrivKey)
  77. // Share (in secret) each other's pubkey & challenge signature
  78. authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature)
  79. if err != nil {
  80. return nil, err
  81. }
  82. remPubKey, remSignature := authSigMsg.Key, authSigMsg.Sig
  83. if !remPubKey.VerifyBytes(challenge[:], remSignature) {
  84. return nil, errors.New("Challenge verification failed")
  85. }
  86. // We've authorized.
  87. sc.remPubKey = remPubKey
  88. return sc, nil
  89. }
  90. // RemotePubKey returns authenticated remote pubkey
  91. func (sc *SecretConnection) RemotePubKey() crypto.PubKey {
  92. return sc.remPubKey
  93. }
  94. // Writes encrypted frames of `sealedFrameSize`
  95. // CONTRACT: data smaller than dataMaxSize is read atomically.
  96. func (sc *SecretConnection) Write(data []byte) (n int, err error) {
  97. for 0 < len(data) {
  98. var frame = make([]byte, totalFrameSize)
  99. var chunk []byte
  100. if dataMaxSize < len(data) {
  101. chunk = data[:dataMaxSize]
  102. data = data[dataMaxSize:]
  103. } else {
  104. chunk = data
  105. data = nil
  106. }
  107. chunkLength := len(chunk)
  108. binary.LittleEndian.PutUint32(frame, uint32(chunkLength))
  109. copy(frame[dataLenSize:], chunk)
  110. aead, err := chacha20poly1305.New(sc.sendSecret[:])
  111. if err != nil {
  112. return n, errors.New("Invalid SecretConnection Key")
  113. }
  114. // encrypt the frame
  115. var sealedFrame = make([]byte, aeadSizeOverhead+totalFrameSize)
  116. aead.Seal(sealedFrame[:0], sc.sendNonce[:], frame, nil)
  117. incrNonce(sc.sendNonce)
  118. // end encryption
  119. _, err = sc.conn.Write(sealedFrame)
  120. if err != nil {
  121. return n, err
  122. }
  123. n += len(chunk)
  124. }
  125. return
  126. }
  127. // CONTRACT: data smaller than dataMaxSize is read atomically.
  128. func (sc *SecretConnection) Read(data []byte) (n int, err error) {
  129. if 0 < len(sc.recvBuffer) {
  130. n = copy(data, sc.recvBuffer)
  131. sc.recvBuffer = sc.recvBuffer[n:]
  132. return
  133. }
  134. aead, err := chacha20poly1305.New(sc.recvSecret[:])
  135. if err != nil {
  136. return n, errors.New("Invalid SecretConnection Key")
  137. }
  138. sealedFrame := make([]byte, totalFrameSize+aeadSizeOverhead)
  139. _, err = io.ReadFull(sc.conn, sealedFrame)
  140. if err != nil {
  141. return
  142. }
  143. // decrypt the frame
  144. var frame = make([]byte, totalFrameSize)
  145. _, err = aead.Open(frame[:0], sc.recvNonce[:], sealedFrame, nil)
  146. if err != nil {
  147. return n, errors.New("Failed to decrypt SecretConnection")
  148. }
  149. incrNonce(sc.recvNonce)
  150. // end decryption
  151. var chunkLength = binary.LittleEndian.Uint32(frame) // read the first four bytes
  152. if chunkLength > dataMaxSize {
  153. return 0, errors.New("chunkLength is greater than dataMaxSize")
  154. }
  155. var chunk = frame[dataLenSize : dataLenSize+chunkLength]
  156. n = copy(data, chunk)
  157. sc.recvBuffer = chunk[n:]
  158. return
  159. }
  160. // Implements net.Conn
  161. // nolint
  162. func (sc *SecretConnection) Close() error { return sc.conn.Close() }
  163. func (sc *SecretConnection) LocalAddr() net.Addr { return sc.conn.(net.Conn).LocalAddr() }
  164. func (sc *SecretConnection) RemoteAddr() net.Addr { return sc.conn.(net.Conn).RemoteAddr() }
  165. func (sc *SecretConnection) SetDeadline(t time.Time) error { return sc.conn.(net.Conn).SetDeadline(t) }
  166. func (sc *SecretConnection) SetReadDeadline(t time.Time) error {
  167. return sc.conn.(net.Conn).SetReadDeadline(t)
  168. }
  169. func (sc *SecretConnection) SetWriteDeadline(t time.Time) error {
  170. return sc.conn.(net.Conn).SetWriteDeadline(t)
  171. }
  172. func genEphKeys() (ephPub, ephPriv *[32]byte) {
  173. var err error
  174. ephPub, ephPriv, err = box.GenerateKey(crand.Reader)
  175. if err != nil {
  176. panic("Could not generate ephemeral keypairs")
  177. }
  178. return
  179. }
  180. func shareEphPubKey(conn io.ReadWriteCloser, locEphPub *[32]byte) (remEphPub *[32]byte, err error) {
  181. // Send our pubkey and receive theirs in tandem.
  182. var trs, _ = cmn.Parallel(
  183. func(_ int) (val interface{}, err error, abort bool) {
  184. var _, err1 = cdc.MarshalBinaryLengthPrefixedWriter(conn, locEphPub)
  185. if err1 != nil {
  186. return nil, err1, true // abort
  187. }
  188. return nil, nil, false
  189. },
  190. func(_ int) (val interface{}, err error, abort bool) {
  191. var _remEphPub [32]byte
  192. var _, err2 = cdc.UnmarshalBinaryLengthPrefixedReader(conn, &_remEphPub, 1024*1024) // TODO
  193. if err2 != nil {
  194. return nil, err2, true // abort
  195. }
  196. return _remEphPub, nil, false
  197. },
  198. )
  199. // If error:
  200. if trs.FirstError() != nil {
  201. err = trs.FirstError()
  202. return
  203. }
  204. // Otherwise:
  205. var _remEphPub = trs.FirstValue().([32]byte)
  206. return &_remEphPub, nil
  207. }
  208. func deriveSecretAndChallenge(dhSecret *[32]byte, locIsLeast bool) (recvSecret, sendSecret *[aeadKeySize]byte, challenge *[32]byte) {
  209. hash := sha256.New
  210. hkdf := hkdf.New(hash, dhSecret[:], nil, []byte("TENDERMINT_SECRET_CONNECTION_KEY_AND_CHALLENGE_GEN"))
  211. // get enough data for 2 aead keys, and a 32 byte challenge
  212. res := new([2*aeadKeySize + 32]byte)
  213. _, err := io.ReadFull(hkdf, res[:])
  214. if err != nil {
  215. panic(err)
  216. }
  217. challenge = new([32]byte)
  218. recvSecret = new([aeadKeySize]byte)
  219. sendSecret = new([aeadKeySize]byte)
  220. // Use the last 32 bytes as the challenge
  221. copy(challenge[:], res[2*aeadKeySize:2*aeadKeySize+32])
  222. // bytes 0 through aeadKeySize - 1 are one aead key.
  223. // bytes aeadKeySize through 2*aeadKeySize -1 are another aead key.
  224. // which key corresponds to sending and receiving key depends on whether
  225. // the local key is less than the remote key.
  226. if locIsLeast {
  227. copy(recvSecret[:], res[0:aeadKeySize])
  228. copy(sendSecret[:], res[aeadKeySize:aeadKeySize*2])
  229. } else {
  230. copy(sendSecret[:], res[0:aeadKeySize])
  231. copy(recvSecret[:], res[aeadKeySize:aeadKeySize*2])
  232. }
  233. return
  234. }
  235. func computeDHSecret(remPubKey, locPrivKey *[32]byte) (shrKey *[32]byte) {
  236. shrKey = new([32]byte)
  237. curve25519.ScalarMult(shrKey, locPrivKey, remPubKey)
  238. return
  239. }
  240. func sort32(foo, bar *[32]byte) (lo, hi *[32]byte) {
  241. if bytes.Compare(foo[:], bar[:]) < 0 {
  242. lo = foo
  243. hi = bar
  244. } else {
  245. lo = bar
  246. hi = foo
  247. }
  248. return
  249. }
  250. func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKey) (signature []byte) {
  251. signature, err := locPrivKey.Sign(challenge[:])
  252. // TODO(ismail): let signChallenge return an error instead
  253. if err != nil {
  254. panic(err)
  255. }
  256. return
  257. }
  258. type authSigMessage struct {
  259. Key crypto.PubKey
  260. Sig []byte
  261. }
  262. func shareAuthSignature(sc *SecretConnection, pubKey crypto.PubKey, signature []byte) (recvMsg authSigMessage, err error) {
  263. // Send our info and receive theirs in tandem.
  264. var trs, _ = cmn.Parallel(
  265. func(_ int) (val interface{}, err error, abort bool) {
  266. var _, err1 = cdc.MarshalBinaryLengthPrefixedWriter(sc, authSigMessage{pubKey, signature})
  267. if err1 != nil {
  268. return nil, err1, true // abort
  269. }
  270. return nil, nil, false
  271. },
  272. func(_ int) (val interface{}, err error, abort bool) {
  273. var _recvMsg authSigMessage
  274. var _, err2 = cdc.UnmarshalBinaryLengthPrefixedReader(sc, &_recvMsg, 1024*1024) // TODO
  275. if err2 != nil {
  276. return nil, err2, true // abort
  277. }
  278. return _recvMsg, nil, false
  279. },
  280. )
  281. // If error:
  282. if trs.FirstError() != nil {
  283. err = trs.FirstError()
  284. return
  285. }
  286. var _recvMsg = trs.FirstValue().(authSigMessage)
  287. return _recvMsg, nil
  288. }
  289. //--------------------------------------------------------------------------------
  290. // Increment nonce little-endian by 1 with wraparound.
  291. // Due to chacha20poly1305 expecting a 12 byte nonce we do not use the first four
  292. // bytes. We only increment a 64 bit unsigned int in the remaining 8 bytes
  293. // (little-endian in nonce[4:]).
  294. func incrNonce(nonce *[aeadNonceSize]byte) {
  295. counter := binary.LittleEndian.Uint64(nonce[4:])
  296. counter++
  297. binary.LittleEndian.PutUint64(nonce[4:], counter)
  298. }