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.

447 lines
13 KiB

p2p: make SecretConnection non-malleable (#3668) ## Issue: This is an approach to fixing secret connection that is more noise-ish than actually noise. but it essentially fixes the problem that #3315 is trying to solve by making the secret connection handshake non-malleable. It's easy to understand and I think will be acceptable to @jaekwon .. the formal reasoning is basically, if the "view" of the transcript between diverges between the sender and the receiver at any point in the protocol, the handshake would terminate. The base protocol of Station to Station mistakenly assumes that if the sender and receiver arrive at shared secret they have the same view. This is only true for a DH on prime order groups. This robustly solves the problem by having each cryptographic operation commit to operators view of the protocol. Another nice thing about a transcript is it provides the basis for "secure" (barring cryptographic breakages, horrible design flaws, or implementation bugs) downgrades, where a backwards compatible handshake can be used to offer newer protocol features/extensions, peers agree to the common subset of what they support, and both sides have to agree on what the other offered for the transcript MAC to verify. With something like Protos/Amino you already get "extensions" for free (TLS uses a simple TLV format https://tools.ietf.org/html/rfc8446#section-4.2 for extensions not too far off from Protos/Amino), so as long as you cryptographically commit to what they contain in the transcript, it should be possible to extend the protocol in a backwards-compatible manner. ## Commits: * Minimal changes to remove malleability of secret connection removes the need to check for lower order points. Breaks compatibility. Secret connections that have no been updated will fail * Remove the redundant blacklist * remove remainders of blacklist in tests to make the code compile again Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Apply suggestions from code review Apply Ismail's error handling Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * fix error check for io.ReadFull Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Bot from GolangCI <42910462+golangcibot@users.noreply.github.com> * update changelog and format the code * move hkdfInit closer to where it's used
5 years ago
p2p: make SecretConnection non-malleable (#3668) ## Issue: This is an approach to fixing secret connection that is more noise-ish than actually noise. but it essentially fixes the problem that #3315 is trying to solve by making the secret connection handshake non-malleable. It's easy to understand and I think will be acceptable to @jaekwon .. the formal reasoning is basically, if the "view" of the transcript between diverges between the sender and the receiver at any point in the protocol, the handshake would terminate. The base protocol of Station to Station mistakenly assumes that if the sender and receiver arrive at shared secret they have the same view. This is only true for a DH on prime order groups. This robustly solves the problem by having each cryptographic operation commit to operators view of the protocol. Another nice thing about a transcript is it provides the basis for "secure" (barring cryptographic breakages, horrible design flaws, or implementation bugs) downgrades, where a backwards compatible handshake can be used to offer newer protocol features/extensions, peers agree to the common subset of what they support, and both sides have to agree on what the other offered for the transcript MAC to verify. With something like Protos/Amino you already get "extensions" for free (TLS uses a simple TLV format https://tools.ietf.org/html/rfc8446#section-4.2 for extensions not too far off from Protos/Amino), so as long as you cryptographically commit to what they contain in the transcript, it should be possible to extend the protocol in a backwards-compatible manner. ## Commits: * Minimal changes to remove malleability of secret connection removes the need to check for lower order points. Breaks compatibility. Secret connections that have no been updated will fail * Remove the redundant blacklist * remove remainders of blacklist in tests to make the code compile again Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Apply suggestions from code review Apply Ismail's error handling Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * fix error check for io.ReadFull Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Bot from GolangCI <42910462+golangcibot@users.noreply.github.com> * update changelog and format the code * move hkdfInit closer to where it's used
5 years ago
p2p: make SecretConnection non-malleable (#3668) ## Issue: This is an approach to fixing secret connection that is more noise-ish than actually noise. but it essentially fixes the problem that #3315 is trying to solve by making the secret connection handshake non-malleable. It's easy to understand and I think will be acceptable to @jaekwon .. the formal reasoning is basically, if the "view" of the transcript between diverges between the sender and the receiver at any point in the protocol, the handshake would terminate. The base protocol of Station to Station mistakenly assumes that if the sender and receiver arrive at shared secret they have the same view. This is only true for a DH on prime order groups. This robustly solves the problem by having each cryptographic operation commit to operators view of the protocol. Another nice thing about a transcript is it provides the basis for "secure" (barring cryptographic breakages, horrible design flaws, or implementation bugs) downgrades, where a backwards compatible handshake can be used to offer newer protocol features/extensions, peers agree to the common subset of what they support, and both sides have to agree on what the other offered for the transcript MAC to verify. With something like Protos/Amino you already get "extensions" for free (TLS uses a simple TLV format https://tools.ietf.org/html/rfc8446#section-4.2 for extensions not too far off from Protos/Amino), so as long as you cryptographically commit to what they contain in the transcript, it should be possible to extend the protocol in a backwards-compatible manner. ## Commits: * Minimal changes to remove malleability of secret connection removes the need to check for lower order points. Breaks compatibility. Secret connections that have no been updated will fail * Remove the redundant blacklist * remove remainders of blacklist in tests to make the code compile again Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Apply suggestions from code review Apply Ismail's error handling Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * fix error check for io.ReadFull Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Bot from GolangCI <42910462+golangcibot@users.noreply.github.com> * update changelog and format the code * move hkdfInit closer to where it's used
5 years ago
p2p: make SecretConnection non-malleable (#3668) ## Issue: This is an approach to fixing secret connection that is more noise-ish than actually noise. but it essentially fixes the problem that #3315 is trying to solve by making the secret connection handshake non-malleable. It's easy to understand and I think will be acceptable to @jaekwon .. the formal reasoning is basically, if the "view" of the transcript between diverges between the sender and the receiver at any point in the protocol, the handshake would terminate. The base protocol of Station to Station mistakenly assumes that if the sender and receiver arrive at shared secret they have the same view. This is only true for a DH on prime order groups. This robustly solves the problem by having each cryptographic operation commit to operators view of the protocol. Another nice thing about a transcript is it provides the basis for "secure" (barring cryptographic breakages, horrible design flaws, or implementation bugs) downgrades, where a backwards compatible handshake can be used to offer newer protocol features/extensions, peers agree to the common subset of what they support, and both sides have to agree on what the other offered for the transcript MAC to verify. With something like Protos/Amino you already get "extensions" for free (TLS uses a simple TLV format https://tools.ietf.org/html/rfc8446#section-4.2 for extensions not too far off from Protos/Amino), so as long as you cryptographically commit to what they contain in the transcript, it should be possible to extend the protocol in a backwards-compatible manner. ## Commits: * Minimal changes to remove malleability of secret connection removes the need to check for lower order points. Breaks compatibility. Secret connections that have no been updated will fail * Remove the redundant blacklist * remove remainders of blacklist in tests to make the code compile again Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Apply suggestions from code review Apply Ismail's error handling Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * fix error check for io.ReadFull Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Bot from GolangCI <42910462+golangcibot@users.noreply.github.com> * update changelog and format the code * move hkdfInit closer to where it's used
5 years ago
p2p: make SecretConnection non-malleable (#3668) ## Issue: This is an approach to fixing secret connection that is more noise-ish than actually noise. but it essentially fixes the problem that #3315 is trying to solve by making the secret connection handshake non-malleable. It's easy to understand and I think will be acceptable to @jaekwon .. the formal reasoning is basically, if the "view" of the transcript between diverges between the sender and the receiver at any point in the protocol, the handshake would terminate. The base protocol of Station to Station mistakenly assumes that if the sender and receiver arrive at shared secret they have the same view. This is only true for a DH on prime order groups. This robustly solves the problem by having each cryptographic operation commit to operators view of the protocol. Another nice thing about a transcript is it provides the basis for "secure" (barring cryptographic breakages, horrible design flaws, or implementation bugs) downgrades, where a backwards compatible handshake can be used to offer newer protocol features/extensions, peers agree to the common subset of what they support, and both sides have to agree on what the other offered for the transcript MAC to verify. With something like Protos/Amino you already get "extensions" for free (TLS uses a simple TLV format https://tools.ietf.org/html/rfc8446#section-4.2 for extensions not too far off from Protos/Amino), so as long as you cryptographically commit to what they contain in the transcript, it should be possible to extend the protocol in a backwards-compatible manner. ## Commits: * Minimal changes to remove malleability of secret connection removes the need to check for lower order points. Breaks compatibility. Secret connections that have no been updated will fail * Remove the redundant blacklist * remove remainders of blacklist in tests to make the code compile again Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Apply suggestions from code review Apply Ismail's error handling Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * fix error check for io.ReadFull Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Bot from GolangCI <42910462+golangcibot@users.noreply.github.com> * update changelog and format the code * move hkdfInit closer to where it's used
5 years ago
p2p: make SecretConnection non-malleable (#3668) ## Issue: This is an approach to fixing secret connection that is more noise-ish than actually noise. but it essentially fixes the problem that #3315 is trying to solve by making the secret connection handshake non-malleable. It's easy to understand and I think will be acceptable to @jaekwon .. the formal reasoning is basically, if the "view" of the transcript between diverges between the sender and the receiver at any point in the protocol, the handshake would terminate. The base protocol of Station to Station mistakenly assumes that if the sender and receiver arrive at shared secret they have the same view. This is only true for a DH on prime order groups. This robustly solves the problem by having each cryptographic operation commit to operators view of the protocol. Another nice thing about a transcript is it provides the basis for "secure" (barring cryptographic breakages, horrible design flaws, or implementation bugs) downgrades, where a backwards compatible handshake can be used to offer newer protocol features/extensions, peers agree to the common subset of what they support, and both sides have to agree on what the other offered for the transcript MAC to verify. With something like Protos/Amino you already get "extensions" for free (TLS uses a simple TLV format https://tools.ietf.org/html/rfc8446#section-4.2 for extensions not too far off from Protos/Amino), so as long as you cryptographically commit to what they contain in the transcript, it should be possible to extend the protocol in a backwards-compatible manner. ## Commits: * Minimal changes to remove malleability of secret connection removes the need to check for lower order points. Breaks compatibility. Secret connections that have no been updated will fail * Remove the redundant blacklist * remove remainders of blacklist in tests to make the code compile again Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Apply suggestions from code review Apply Ismail's error handling Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * fix error check for io.ReadFull Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Bot from GolangCI <42910462+golangcibot@users.noreply.github.com> * update changelog and format the code * move hkdfInit closer to where it's used
5 years ago
p2p: make SecretConnection non-malleable (#3668) ## Issue: This is an approach to fixing secret connection that is more noise-ish than actually noise. but it essentially fixes the problem that #3315 is trying to solve by making the secret connection handshake non-malleable. It's easy to understand and I think will be acceptable to @jaekwon .. the formal reasoning is basically, if the "view" of the transcript between diverges between the sender and the receiver at any point in the protocol, the handshake would terminate. The base protocol of Station to Station mistakenly assumes that if the sender and receiver arrive at shared secret they have the same view. This is only true for a DH on prime order groups. This robustly solves the problem by having each cryptographic operation commit to operators view of the protocol. Another nice thing about a transcript is it provides the basis for "secure" (barring cryptographic breakages, horrible design flaws, or implementation bugs) downgrades, where a backwards compatible handshake can be used to offer newer protocol features/extensions, peers agree to the common subset of what they support, and both sides have to agree on what the other offered for the transcript MAC to verify. With something like Protos/Amino you already get "extensions" for free (TLS uses a simple TLV format https://tools.ietf.org/html/rfc8446#section-4.2 for extensions not too far off from Protos/Amino), so as long as you cryptographically commit to what they contain in the transcript, it should be possible to extend the protocol in a backwards-compatible manner. ## Commits: * Minimal changes to remove malleability of secret connection removes the need to check for lower order points. Breaks compatibility. Secret connections that have no been updated will fail * Remove the redundant blacklist * remove remainders of blacklist in tests to make the code compile again Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Apply suggestions from code review Apply Ismail's error handling Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * fix error check for io.ReadFull Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Bot from GolangCI <42910462+golangcibot@users.noreply.github.com> * update changelog and format the code * move hkdfInit closer to where it's used
5 years ago
p2p: make SecretConnection non-malleable (#3668) ## Issue: This is an approach to fixing secret connection that is more noise-ish than actually noise. but it essentially fixes the problem that #3315 is trying to solve by making the secret connection handshake non-malleable. It's easy to understand and I think will be acceptable to @jaekwon .. the formal reasoning is basically, if the "view" of the transcript between diverges between the sender and the receiver at any point in the protocol, the handshake would terminate. The base protocol of Station to Station mistakenly assumes that if the sender and receiver arrive at shared secret they have the same view. This is only true for a DH on prime order groups. This robustly solves the problem by having each cryptographic operation commit to operators view of the protocol. Another nice thing about a transcript is it provides the basis for "secure" (barring cryptographic breakages, horrible design flaws, or implementation bugs) downgrades, where a backwards compatible handshake can be used to offer newer protocol features/extensions, peers agree to the common subset of what they support, and both sides have to agree on what the other offered for the transcript MAC to verify. With something like Protos/Amino you already get "extensions" for free (TLS uses a simple TLV format https://tools.ietf.org/html/rfc8446#section-4.2 for extensions not too far off from Protos/Amino), so as long as you cryptographically commit to what they contain in the transcript, it should be possible to extend the protocol in a backwards-compatible manner. ## Commits: * Minimal changes to remove malleability of secret connection removes the need to check for lower order points. Breaks compatibility. Secret connections that have no been updated will fail * Remove the redundant blacklist * remove remainders of blacklist in tests to make the code compile again Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Apply suggestions from code review Apply Ismail's error handling Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * fix error check for io.ReadFull Signed-off-by: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Ismail Khoffi <Ismail.Khoffi@gmail.com> * Update p2p/conn/secret_connection.go Co-Authored-By: Bot from GolangCI <42910462+golangcibot@users.noreply.github.com> * update changelog and format the code * move hkdfInit closer to where it's used
5 years ago
  1. package conn
  2. import (
  3. "bytes"
  4. "crypto/cipher"
  5. crand "crypto/rand"
  6. "crypto/sha256"
  7. "crypto/subtle"
  8. "encoding/binary"
  9. "io"
  10. "math"
  11. "net"
  12. "sync"
  13. "time"
  14. "github.com/gtank/merlin"
  15. pool "github.com/libp2p/go-buffer-pool"
  16. "github.com/pkg/errors"
  17. "golang.org/x/crypto/chacha20poly1305"
  18. "golang.org/x/crypto/curve25519"
  19. "golang.org/x/crypto/hkdf"
  20. "golang.org/x/crypto/nacl/box"
  21. "github.com/tendermint/tendermint/crypto"
  22. "github.com/tendermint/tendermint/crypto/ed25519"
  23. "github.com/tendermint/tendermint/libs/async"
  24. )
  25. // 4 + 1024 == 1028 total frame size
  26. const (
  27. dataLenSize = 4
  28. dataMaxSize = 1024
  29. totalFrameSize = dataMaxSize + dataLenSize
  30. aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag
  31. aeadKeySize = chacha20poly1305.KeySize
  32. aeadNonceSize = chacha20poly1305.NonceSize
  33. )
  34. var (
  35. ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer")
  36. ErrSharedSecretIsZero = errors.New("shared secret is all zeroes")
  37. labelEphemeralLowerPublicKey = []byte("EPHEMERAL_LOWER_PUBLIC_KEY")
  38. labelEphemeralUpperPublicKey = []byte("EPHEMERAL_UPPER_PUBLIC_KEY")
  39. labelDHSecret = []byte("DH_SECRET")
  40. labelSecretConnectionMac = []byte("SECRET_CONNECTION_MAC")
  41. secretConnKeyAndChallengeGen = []byte("TENDERMINT_SECRET_CONNECTION_KEY_AND_CHALLENGE_GEN")
  42. )
  43. // SecretConnection implements net.Conn.
  44. // It is an implementation of the STS protocol.
  45. // See https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf for
  46. // details on the protocol.
  47. //
  48. // Consumers of the SecretConnection are responsible for authenticating
  49. // the remote peer's pubkey against known information, like a nodeID.
  50. // Otherwise they are vulnerable to MITM.
  51. // (TODO(ismail): see also https://github.com/tendermint/tendermint/issues/3010)
  52. type SecretConnection struct {
  53. // immutable
  54. recvAead cipher.AEAD
  55. sendAead cipher.AEAD
  56. remPubKey crypto.PubKey
  57. conn io.ReadWriteCloser
  58. // net.Conn must be thread safe:
  59. // https://golang.org/pkg/net/#Conn.
  60. // Since we have internal mutable state,
  61. // we need mtxs. But recv and send states
  62. // are independent, so we can use two mtxs.
  63. // All .Read are covered by recvMtx,
  64. // all .Write are covered by sendMtx.
  65. recvMtx sync.Mutex
  66. recvBuffer []byte
  67. recvNonce *[aeadNonceSize]byte
  68. sendMtx sync.Mutex
  69. sendNonce *[aeadNonceSize]byte
  70. }
  71. // MakeSecretConnection performs handshake and returns a new authenticated
  72. // SecretConnection.
  73. // Returns nil if there is an error in handshake.
  74. // Caller should call conn.Close()
  75. // See docs/sts-final.pdf for more information.
  76. func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*SecretConnection, error) {
  77. var (
  78. locPubKey = locPrivKey.PubKey()
  79. )
  80. // Generate ephemeral keys for perfect forward secrecy.
  81. locEphPub, locEphPriv := genEphKeys()
  82. // Write local ephemeral pubkey and receive one too.
  83. // NOTE: every 32-byte string is accepted as a Curve25519 public key (see
  84. // DJB's Curve25519 paper: http://cr.yp.to/ecdh/curve25519-20060209.pdf)
  85. remEphPub, err := shareEphPubKey(conn, locEphPub)
  86. if err != nil {
  87. return nil, err
  88. }
  89. // Sort by lexical order.
  90. loEphPub, hiEphPub := sort32(locEphPub, remEphPub)
  91. transcript := merlin.NewTranscript("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH")
  92. transcript.AppendMessage(labelEphemeralLowerPublicKey, loEphPub[:])
  93. transcript.AppendMessage(labelEphemeralUpperPublicKey, hiEphPub[:])
  94. // Check if the local ephemeral public key was the least, lexicographically
  95. // sorted.
  96. locIsLeast := bytes.Equal(locEphPub[:], loEphPub[:])
  97. // Compute common diffie hellman secret using X25519.
  98. dhSecret, err := computeDHSecret(remEphPub, locEphPriv)
  99. if err != nil {
  100. return nil, err
  101. }
  102. transcript.AppendMessage(labelDHSecret, dhSecret[:])
  103. // Generate the secret used for receiving, sending, challenge via HKDF-SHA2
  104. // on the transcript state (which itself also uses HKDF-SHA2 to derive a key
  105. // from the dhSecret).
  106. recvSecret, sendSecret := deriveSecrets(dhSecret, locIsLeast)
  107. const challengeSize = 32
  108. var challenge [challengeSize]byte
  109. challengeSlice := transcript.ExtractBytes(labelSecretConnectionMac, challengeSize)
  110. copy(challenge[:], challengeSlice[0:challengeSize])
  111. sendAead, err := chacha20poly1305.New(sendSecret[:])
  112. if err != nil {
  113. return nil, errors.New("invalid send SecretConnection Key")
  114. }
  115. recvAead, err := chacha20poly1305.New(recvSecret[:])
  116. if err != nil {
  117. return nil, errors.New("invalid receive SecretConnection Key")
  118. }
  119. sc := &SecretConnection{
  120. conn: conn,
  121. recvBuffer: nil,
  122. recvNonce: new([aeadNonceSize]byte),
  123. sendNonce: new([aeadNonceSize]byte),
  124. recvAead: recvAead,
  125. sendAead: sendAead,
  126. }
  127. // Sign the challenge bytes for authentication.
  128. locSignature := signChallenge(&challenge, locPrivKey)
  129. // Share (in secret) each other's pubkey & challenge signature
  130. authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature)
  131. if err != nil {
  132. return nil, err
  133. }
  134. remPubKey, remSignature := authSigMsg.Key, authSigMsg.Sig
  135. if _, ok := remPubKey.(ed25519.PubKeyEd25519); !ok {
  136. return nil, errors.Errorf("expected ed25519 pubkey, got %T", remPubKey)
  137. }
  138. if !remPubKey.VerifyBytes(challenge[:], remSignature) {
  139. return nil, errors.New("challenge verification failed")
  140. }
  141. // We've authorized.
  142. sc.remPubKey = remPubKey
  143. return sc, nil
  144. }
  145. // RemotePubKey returns authenticated remote pubkey
  146. func (sc *SecretConnection) RemotePubKey() crypto.PubKey {
  147. return sc.remPubKey
  148. }
  149. // Writes encrypted frames of `totalFrameSize + aeadSizeOverhead`.
  150. // CONTRACT: data smaller than dataMaxSize is written atomically.
  151. func (sc *SecretConnection) Write(data []byte) (n int, err error) {
  152. sc.sendMtx.Lock()
  153. defer sc.sendMtx.Unlock()
  154. for 0 < len(data) {
  155. if err := func() error {
  156. var sealedFrame = pool.Get(aeadSizeOverhead + totalFrameSize)
  157. var frame = pool.Get(totalFrameSize)
  158. defer func() {
  159. pool.Put(sealedFrame)
  160. pool.Put(frame)
  161. }()
  162. var chunk []byte
  163. if dataMaxSize < len(data) {
  164. chunk = data[:dataMaxSize]
  165. data = data[dataMaxSize:]
  166. } else {
  167. chunk = data
  168. data = nil
  169. }
  170. chunkLength := len(chunk)
  171. binary.LittleEndian.PutUint32(frame, uint32(chunkLength))
  172. copy(frame[dataLenSize:], chunk)
  173. // encrypt the frame
  174. sc.sendAead.Seal(sealedFrame[:0], sc.sendNonce[:], frame, nil)
  175. incrNonce(sc.sendNonce)
  176. // end encryption
  177. _, err = sc.conn.Write(sealedFrame)
  178. if err != nil {
  179. return err
  180. }
  181. n += len(chunk)
  182. return nil
  183. }(); err != nil {
  184. return n, err
  185. }
  186. }
  187. return n, err
  188. }
  189. // CONTRACT: data smaller than dataMaxSize is read atomically.
  190. func (sc *SecretConnection) Read(data []byte) (n int, err error) {
  191. sc.recvMtx.Lock()
  192. defer sc.recvMtx.Unlock()
  193. // read off and update the recvBuffer, if non-empty
  194. if 0 < len(sc.recvBuffer) {
  195. n = copy(data, sc.recvBuffer)
  196. sc.recvBuffer = sc.recvBuffer[n:]
  197. return
  198. }
  199. // read off the conn
  200. var sealedFrame = pool.Get(aeadSizeOverhead + totalFrameSize)
  201. defer pool.Put(sealedFrame)
  202. _, err = io.ReadFull(sc.conn, sealedFrame)
  203. if err != nil {
  204. return
  205. }
  206. // decrypt the frame.
  207. // reads and updates the sc.recvNonce
  208. var frame = pool.Get(totalFrameSize)
  209. defer pool.Put(frame)
  210. _, err = sc.recvAead.Open(frame[:0], sc.recvNonce[:], sealedFrame, nil)
  211. if err != nil {
  212. return n, errors.New("failed to decrypt SecretConnection")
  213. }
  214. incrNonce(sc.recvNonce)
  215. // end decryption
  216. // copy checkLength worth into data,
  217. // set recvBuffer to the rest.
  218. var chunkLength = binary.LittleEndian.Uint32(frame) // read the first four bytes
  219. if chunkLength > dataMaxSize {
  220. return 0, errors.New("chunkLength is greater than dataMaxSize")
  221. }
  222. var chunk = frame[dataLenSize : dataLenSize+chunkLength]
  223. n = copy(data, chunk)
  224. if n < len(chunk) {
  225. sc.recvBuffer = make([]byte, len(chunk)-n)
  226. copy(sc.recvBuffer, chunk[n:])
  227. }
  228. return n, err
  229. }
  230. // Implements net.Conn
  231. // nolint
  232. func (sc *SecretConnection) Close() error { return sc.conn.Close() }
  233. func (sc *SecretConnection) LocalAddr() net.Addr { return sc.conn.(net.Conn).LocalAddr() }
  234. func (sc *SecretConnection) RemoteAddr() net.Addr { return sc.conn.(net.Conn).RemoteAddr() }
  235. func (sc *SecretConnection) SetDeadline(t time.Time) error { return sc.conn.(net.Conn).SetDeadline(t) }
  236. func (sc *SecretConnection) SetReadDeadline(t time.Time) error {
  237. return sc.conn.(net.Conn).SetReadDeadline(t)
  238. }
  239. func (sc *SecretConnection) SetWriteDeadline(t time.Time) error {
  240. return sc.conn.(net.Conn).SetWriteDeadline(t)
  241. }
  242. func genEphKeys() (ephPub, ephPriv *[32]byte) {
  243. var err error
  244. // TODO: Probably not a problem but ask Tony: different from the rust implementation (uses x25519-dalek),
  245. // we do not "clamp" the private key scalar:
  246. // see: https://github.com/dalek-cryptography/x25519-dalek/blob/34676d336049df2bba763cc076a75e47ae1f170f/src/x25519.rs#L56-L74
  247. ephPub, ephPriv, err = box.GenerateKey(crand.Reader)
  248. if err != nil {
  249. panic("Could not generate ephemeral key-pair")
  250. }
  251. return
  252. }
  253. func shareEphPubKey(conn io.ReadWriter, locEphPub *[32]byte) (remEphPub *[32]byte, err error) {
  254. // Send our pubkey and receive theirs in tandem.
  255. var trs, _ = async.Parallel(
  256. func(_ int) (val interface{}, abort bool, err error) {
  257. var _, err1 = cdc.MarshalBinaryLengthPrefixedWriter(conn, locEphPub)
  258. if err1 != nil {
  259. return nil, true, err1 // abort
  260. }
  261. return nil, false, nil
  262. },
  263. func(_ int) (val interface{}, abort bool, err error) {
  264. var _remEphPub [32]byte
  265. var _, err2 = cdc.UnmarshalBinaryLengthPrefixedReader(conn, &_remEphPub, 1024*1024) // TODO
  266. if err2 != nil {
  267. return nil, true, err2 // abort
  268. }
  269. return _remEphPub, false, nil
  270. },
  271. )
  272. // If error:
  273. if trs.FirstError() != nil {
  274. err = trs.FirstError()
  275. return
  276. }
  277. // Otherwise:
  278. var _remEphPub = trs.FirstValue().([32]byte)
  279. return &_remEphPub, nil
  280. }
  281. func deriveSecrets(
  282. dhSecret *[32]byte,
  283. locIsLeast bool,
  284. ) (recvSecret, sendSecret *[aeadKeySize]byte) {
  285. hash := sha256.New
  286. hkdf := hkdf.New(hash, dhSecret[:], nil, secretConnKeyAndChallengeGen)
  287. // get enough data for 2 aead keys, and a 32 byte challenge
  288. res := new([2*aeadKeySize + 32]byte)
  289. _, err := io.ReadFull(hkdf, res[:])
  290. if err != nil {
  291. panic(err)
  292. }
  293. recvSecret = new([aeadKeySize]byte)
  294. sendSecret = new([aeadKeySize]byte)
  295. // bytes 0 through aeadKeySize - 1 are one aead key.
  296. // bytes aeadKeySize through 2*aeadKeySize -1 are another aead key.
  297. // which key corresponds to sending and receiving key depends on whether
  298. // the local key is less than the remote key.
  299. if locIsLeast {
  300. copy(recvSecret[:], res[0:aeadKeySize])
  301. copy(sendSecret[:], res[aeadKeySize:aeadKeySize*2])
  302. } else {
  303. copy(sendSecret[:], res[0:aeadKeySize])
  304. copy(recvSecret[:], res[aeadKeySize:aeadKeySize*2])
  305. }
  306. return
  307. }
  308. // computeDHSecret computes a Diffie-Hellman shared secret key
  309. // from our own local private key and the other's public key.
  310. //
  311. // It returns an error if the computed shared secret is all zeroes.
  312. func computeDHSecret(remPubKey, locPrivKey *[32]byte) (shrKey *[32]byte, err error) {
  313. shrKey = new([32]byte)
  314. curve25519.ScalarMult(shrKey, locPrivKey, remPubKey)
  315. // reject if the returned shared secret is all zeroes
  316. // related to: https://github.com/tendermint/tendermint/issues/3010
  317. zero := new([32]byte)
  318. if subtle.ConstantTimeCompare(shrKey[:], zero[:]) == 1 {
  319. return nil, ErrSharedSecretIsZero
  320. }
  321. return
  322. }
  323. func sort32(foo, bar *[32]byte) (lo, hi *[32]byte) {
  324. if bytes.Compare(foo[:], bar[:]) < 0 {
  325. lo = foo
  326. hi = bar
  327. } else {
  328. lo = bar
  329. hi = foo
  330. }
  331. return
  332. }
  333. func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKey) (signature []byte) {
  334. signature, err := locPrivKey.Sign(challenge[:])
  335. // TODO(ismail): let signChallenge return an error instead
  336. if err != nil {
  337. panic(err)
  338. }
  339. return
  340. }
  341. type authSigMessage struct {
  342. Key crypto.PubKey
  343. Sig []byte
  344. }
  345. func shareAuthSignature(sc io.ReadWriter, pubKey crypto.PubKey, signature []byte) (recvMsg authSigMessage, err error) {
  346. // Send our info and receive theirs in tandem.
  347. var trs, _ = async.Parallel(
  348. func(_ int) (val interface{}, abort bool, err error) {
  349. var _, err1 = cdc.MarshalBinaryLengthPrefixedWriter(sc, authSigMessage{pubKey, signature})
  350. if err1 != nil {
  351. return nil, true, err1 // abort
  352. }
  353. return nil, false, nil
  354. },
  355. func(_ int) (val interface{}, abort bool, err error) {
  356. var _recvMsg authSigMessage
  357. var _, err2 = cdc.UnmarshalBinaryLengthPrefixedReader(sc, &_recvMsg, 1024*1024) // TODO
  358. if err2 != nil {
  359. return nil, true, err2 // abort
  360. }
  361. return _recvMsg, false, nil
  362. },
  363. )
  364. // If error:
  365. if trs.FirstError() != nil {
  366. err = trs.FirstError()
  367. return
  368. }
  369. var _recvMsg = trs.FirstValue().(authSigMessage)
  370. return _recvMsg, nil
  371. }
  372. //--------------------------------------------------------------------------------
  373. // Increment nonce little-endian by 1 with wraparound.
  374. // Due to chacha20poly1305 expecting a 12 byte nonce we do not use the first four
  375. // bytes. We only increment a 64 bit unsigned int in the remaining 8 bytes
  376. // (little-endian in nonce[4:]).
  377. func incrNonce(nonce *[aeadNonceSize]byte) {
  378. counter := binary.LittleEndian.Uint64(nonce[4:])
  379. if counter == math.MaxUint64 {
  380. // Terminates the session and makes sure the nonce would not re-used.
  381. // See https://github.com/tendermint/tendermint/issues/3531
  382. panic("can't increase nonce without overflow")
  383. }
  384. counter++
  385. binary.LittleEndian.PutUint64(nonce[4:], counter)
  386. }