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.

271 lines
6.4 KiB

crypto: Use a different library for ed25519/sr25519 (#6526) At Oasis we have spend some time writing a new Ed25519/X25519/sr25519 implementation called curve25519-voi. This PR switches the import from ed25519consensus/go-schnorrkel, which should lead to performance gains on most systems. Summary of changes: * curve25519-voi is now used for Ed25519 operations, following the existing ZIP-215 semantics. * curve25519-voi's public key cache is enabled (hardcoded size of 4096 entries, should be tuned, see the code comment) to accelerate repeated Ed25519 verification with the same public key(s). * (BREAKING) curve25519-voi is now used for sr25519 operations. This is a breaking change as the current sr25519 support does something decidedly non-standard when going from a MiniSecretKey to a SecretKey and or PublicKey (The expansion routine is called twice). While I believe the new behavior (that expands once and only once) to be more "correct", this changes the semantics as implemented. * curve25519-voi is now used for merlin since the included STROBE implementation produces much less garbage on the heap. Side issues fixed: * The version of go-schnorrkel that is currently imported by tendermint has a badly broken batch verification implementation. Upstream has fixed the issue after I reported it, so the version should be bumped in the interim. Open design questions/issues: * As noted, the public key cache size should be tuned. It is currently backed by a trivial thread-safe LRU cache, which is not scan-resistant, but replacing it with something better is a matter of implementing an interface. * As far as I can tell, the only reason why serial verification on batch failure is necessary is to provide more detailed error messages (that are only used in some unit tests). If you trust the batch verification to be consistent with serial verification then the fallback can be eliminated entirely (the BatchVerifier provided by the new library supports an option that omits the fallback if this is chosen as the way forward). * curve25519-voi's sr25519 support could use more optimization and more eyes on the code. The algorithm unfortunately is woefully under-specified, and the implementation was done primarily because I got really sad when I actually looked at go-schnorrkel, and we do not use the algorithm at this time.
4 years ago
cleanup: Reduce and normalize import path aliasing. (#6975) The code in the Tendermint repository makes heavy use of import aliasing. This is made necessary by our extensive reuse of common base package names, and by repetition of similar names across different subdirectories. Unfortunately we have not been very consistent about which packages we alias in various circumstances, and the aliases we use vary. In the spirit of the advice in the style guide and https://github.com/golang/go/wiki/CodeReviewComments#imports, his change makes an effort to clean up and normalize import aliasing. This change makes no API or behavioral changes. It is a pure cleanup intended o help make the code more readable to developers (including myself) trying to understand what is being imported where. Only unexported names have been modified, and the changes were generated and applied mechanically with gofmt -r and comby, respecting the lexical and syntactic rules of Go. Even so, I did not fix every inconsistency. Where the changes would be too disruptive, I left it alone. The principles I followed in this cleanup are: - Remove aliases that restate the package name. - Remove aliases where the base package name is unambiguous. - Move overly-terse abbreviations from the import to the usage site. - Fix lexical issues (remove underscores, remove capitalization). - Fix import groupings to more closely match the style guide. - Group blank (side-effecting) imports and ensure they are commented. - Add aliases to multiple imports with the same base package name.
3 years ago
cleanup: Reduce and normalize import path aliasing. (#6975) The code in the Tendermint repository makes heavy use of import aliasing. This is made necessary by our extensive reuse of common base package names, and by repetition of similar names across different subdirectories. Unfortunately we have not been very consistent about which packages we alias in various circumstances, and the aliases we use vary. In the spirit of the advice in the style guide and https://github.com/golang/go/wiki/CodeReviewComments#imports, his change makes an effort to clean up and normalize import aliasing. This change makes no API or behavioral changes. It is a pure cleanup intended o help make the code more readable to developers (including myself) trying to understand what is being imported where. Only unexported names have been modified, and the changes were generated and applied mechanically with gofmt -r and comby, respecting the lexical and syntactic rules of Go. Even so, I did not fix every inconsistency. Where the changes would be too disruptive, I left it alone. The principles I followed in this cleanup are: - Remove aliases that restate the package name. - Remove aliases where the base package name is unambiguous. - Move overly-terse abbreviations from the import to the usage site. - Fix lexical issues (remove underscores, remove capitalization). - Fix import groupings to more closely match the style guide. - Group blank (side-effecting) imports and ensure they are commented. - Add aliases to multiple imports with the same base package name.
3 years ago
crypto: Use a different library for ed25519/sr25519 (#6526) At Oasis we have spend some time writing a new Ed25519/X25519/sr25519 implementation called curve25519-voi. This PR switches the import from ed25519consensus/go-schnorrkel, which should lead to performance gains on most systems. Summary of changes: * curve25519-voi is now used for Ed25519 operations, following the existing ZIP-215 semantics. * curve25519-voi's public key cache is enabled (hardcoded size of 4096 entries, should be tuned, see the code comment) to accelerate repeated Ed25519 verification with the same public key(s). * (BREAKING) curve25519-voi is now used for sr25519 operations. This is a breaking change as the current sr25519 support does something decidedly non-standard when going from a MiniSecretKey to a SecretKey and or PublicKey (The expansion routine is called twice). While I believe the new behavior (that expands once and only once) to be more "correct", this changes the semantics as implemented. * curve25519-voi is now used for merlin since the included STROBE implementation produces much less garbage on the heap. Side issues fixed: * The version of go-schnorrkel that is currently imported by tendermint has a badly broken batch verification implementation. Upstream has fixed the issue after I reported it, so the version should be bumped in the interim. Open design questions/issues: * As noted, the public key cache size should be tuned. It is currently backed by a trivial thread-safe LRU cache, which is not scan-resistant, but replacing it with something better is a matter of implementing an interface. * As far as I can tell, the only reason why serial verification on batch failure is necessary is to provide more detailed error messages (that are only used in some unit tests). If you trust the batch verification to be consistent with serial verification then the fallback can be eliminated entirely (the BatchVerifier provided by the new library supports an option that omits the fallback if this is chosen as the way forward). * curve25519-voi's sr25519 support could use more optimization and more eyes on the code. The algorithm unfortunately is woefully under-specified, and the implementation was done primarily because I got really sad when I actually looked at go-schnorrkel, and we do not use the algorithm at this time.
4 years ago
  1. package conn
  2. import (
  3. "bytes"
  4. "errors"
  5. "io"
  6. "testing"
  7. gogotypes "github.com/gogo/protobuf/types"
  8. "github.com/oasisprotocol/curve25519-voi/primitives/merlin"
  9. "github.com/stretchr/testify/assert"
  10. "golang.org/x/crypto/chacha20poly1305"
  11. "github.com/tendermint/tendermint/crypto"
  12. "github.com/tendermint/tendermint/crypto/ed25519"
  13. "github.com/tendermint/tendermint/crypto/encoding"
  14. "github.com/tendermint/tendermint/internal/libs/protoio"
  15. tmp2p "github.com/tendermint/tendermint/proto/tendermint/p2p"
  16. )
  17. type buffer struct {
  18. next bytes.Buffer
  19. }
  20. func (b *buffer) Read(data []byte) (n int, err error) {
  21. return b.next.Read(data)
  22. }
  23. func (b *buffer) Write(data []byte) (n int, err error) {
  24. return b.next.Write(data)
  25. }
  26. func (b *buffer) Bytes() []byte {
  27. return b.next.Bytes()
  28. }
  29. func (b *buffer) Close() error {
  30. return nil
  31. }
  32. type evilConn struct {
  33. secretConn *SecretConnection
  34. buffer *buffer
  35. locEphPub *[32]byte
  36. locEphPriv *[32]byte
  37. remEphPub *[32]byte
  38. privKey crypto.PrivKey
  39. readStep int
  40. writeStep int
  41. readOffset int
  42. shareEphKey bool
  43. badEphKey bool
  44. shareAuthSignature bool
  45. badAuthSignature bool
  46. }
  47. func newEvilConn(shareEphKey, badEphKey, shareAuthSignature, badAuthSignature bool) *evilConn {
  48. privKey := ed25519.GenPrivKey()
  49. locEphPub, locEphPriv := genEphKeys()
  50. var rep [32]byte
  51. c := &evilConn{
  52. locEphPub: locEphPub,
  53. locEphPriv: locEphPriv,
  54. remEphPub: &rep,
  55. privKey: privKey,
  56. shareEphKey: shareEphKey,
  57. badEphKey: badEphKey,
  58. shareAuthSignature: shareAuthSignature,
  59. badAuthSignature: badAuthSignature,
  60. }
  61. return c
  62. }
  63. func (c *evilConn) Read(data []byte) (n int, err error) {
  64. if !c.shareEphKey {
  65. return 0, io.EOF
  66. }
  67. switch c.readStep {
  68. case 0:
  69. if !c.badEphKey {
  70. lc := *c.locEphPub
  71. bz, err := protoio.MarshalDelimited(&gogotypes.BytesValue{Value: lc[:]})
  72. if err != nil {
  73. panic(err)
  74. }
  75. copy(data, bz[c.readOffset:])
  76. n = len(data)
  77. } else {
  78. bz, err := protoio.MarshalDelimited(&gogotypes.BytesValue{Value: []byte("drop users;")})
  79. if err != nil {
  80. panic(err)
  81. }
  82. copy(data, bz)
  83. n = len(data)
  84. }
  85. c.readOffset += n
  86. if n >= 32 {
  87. c.readOffset = 0
  88. c.readStep = 1
  89. if !c.shareAuthSignature {
  90. c.readStep = 2
  91. }
  92. }
  93. return n, nil
  94. case 1:
  95. signature := c.signChallenge()
  96. if !c.badAuthSignature {
  97. pkpb, err := encoding.PubKeyToProto(c.privKey.PubKey())
  98. if err != nil {
  99. panic(err)
  100. }
  101. bz, err := protoio.MarshalDelimited(&tmp2p.AuthSigMessage{PubKey: pkpb, Sig: signature})
  102. if err != nil {
  103. panic(err)
  104. }
  105. n, err = c.secretConn.Write(bz)
  106. if err != nil {
  107. panic(err)
  108. }
  109. if c.readOffset > len(c.buffer.Bytes()) {
  110. return len(data), nil
  111. }
  112. copy(data, c.buffer.Bytes()[c.readOffset:])
  113. } else {
  114. bz, err := protoio.MarshalDelimited(&gogotypes.BytesValue{Value: []byte("select * from users;")})
  115. if err != nil {
  116. panic(err)
  117. }
  118. n, err = c.secretConn.Write(bz)
  119. if err != nil {
  120. panic(err)
  121. }
  122. if c.readOffset > len(c.buffer.Bytes()) {
  123. return len(data), nil
  124. }
  125. copy(data, c.buffer.Bytes())
  126. }
  127. c.readOffset += len(data)
  128. return n, nil
  129. default:
  130. return 0, io.EOF
  131. }
  132. }
  133. func (c *evilConn) Write(data []byte) (n int, err error) {
  134. switch c.writeStep {
  135. case 0:
  136. var (
  137. bytes gogotypes.BytesValue
  138. remEphPub [32]byte
  139. )
  140. err := protoio.UnmarshalDelimited(data, &bytes)
  141. if err != nil {
  142. panic(err)
  143. }
  144. copy(remEphPub[:], bytes.Value)
  145. c.remEphPub = &remEphPub
  146. c.writeStep = 1
  147. if !c.shareAuthSignature {
  148. c.writeStep = 2
  149. }
  150. return len(data), nil
  151. case 1:
  152. // Signature is not needed, therefore skipped.
  153. return len(data), nil
  154. default:
  155. return 0, io.EOF
  156. }
  157. }
  158. func (c *evilConn) Close() error {
  159. return nil
  160. }
  161. func (c *evilConn) signChallenge() []byte {
  162. // Sort by lexical order.
  163. loEphPub, hiEphPub := sort32(c.locEphPub, c.remEphPub)
  164. transcript := merlin.NewTranscript("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH")
  165. transcript.AppendMessage(labelEphemeralLowerPublicKey, loEphPub[:])
  166. transcript.AppendMessage(labelEphemeralUpperPublicKey, hiEphPub[:])
  167. // Check if the local ephemeral public key was the least, lexicographically
  168. // sorted.
  169. locIsLeast := bytes.Equal(c.locEphPub[:], loEphPub[:])
  170. // Compute common diffie hellman secret using X25519.
  171. dhSecret, err := computeDHSecret(c.remEphPub, c.locEphPriv)
  172. if err != nil {
  173. panic(err)
  174. }
  175. transcript.AppendMessage(labelDHSecret, dhSecret[:])
  176. // Generate the secret used for receiving, sending, challenge via HKDF-SHA2
  177. // on the transcript state (which itself also uses HKDF-SHA2 to derive a key
  178. // from the dhSecret).
  179. recvSecret, sendSecret := deriveSecrets(dhSecret, locIsLeast)
  180. const challengeSize = 32
  181. var challenge [challengeSize]byte
  182. transcript.ExtractBytes(challenge[:], labelSecretConnectionMac)
  183. sendAead, err := chacha20poly1305.New(sendSecret[:])
  184. if err != nil {
  185. panic(errors.New("invalid send SecretConnection Key"))
  186. }
  187. recvAead, err := chacha20poly1305.New(recvSecret[:])
  188. if err != nil {
  189. panic(errors.New("invalid receive SecretConnection Key"))
  190. }
  191. b := &buffer{}
  192. c.secretConn = &SecretConnection{
  193. conn: b,
  194. recvBuffer: nil,
  195. recvNonce: new([aeadNonceSize]byte),
  196. sendNonce: new([aeadNonceSize]byte),
  197. recvAead: recvAead,
  198. sendAead: sendAead,
  199. }
  200. c.buffer = b
  201. // Sign the challenge bytes for authentication.
  202. locSignature, err := signChallenge(&challenge, c.privKey)
  203. if err != nil {
  204. panic(err)
  205. }
  206. return locSignature
  207. }
  208. // TestMakeSecretConnection creates an evil connection and tests that
  209. // MakeSecretConnection errors at different stages.
  210. func TestMakeSecretConnection(t *testing.T) {
  211. testCases := []struct {
  212. name string
  213. conn *evilConn
  214. errMsg string
  215. }{
  216. {"refuse to share ethimeral key", newEvilConn(false, false, false, false), "EOF"},
  217. {"share bad ethimeral key", newEvilConn(true, true, false, false), "wrong wireType"},
  218. {"refuse to share auth signature", newEvilConn(true, false, false, false), "EOF"},
  219. {"share bad auth signature", newEvilConn(true, false, true, true), "failed to decrypt SecretConnection"},
  220. {"all good", newEvilConn(true, false, true, false), ""},
  221. }
  222. for _, tc := range testCases {
  223. tc := tc
  224. t.Run(tc.name, func(t *testing.T) {
  225. privKey := ed25519.GenPrivKey()
  226. _, err := MakeSecretConnection(tc.conn, privKey)
  227. if tc.errMsg != "" {
  228. if assert.Error(t, err) {
  229. assert.Contains(t, err.Error(), tc.errMsg)
  230. }
  231. } else {
  232. assert.NoError(t, err)
  233. }
  234. })
  235. }
  236. }