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