cipher_state_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package noiseutil
  2. import (
  3. "math"
  4. "testing"
  5. "github.com/flynn/noise"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/require"
  8. )
  9. func TestCipherStateAESGCMRoundtrip(t *testing.T) {
  10. enc, dec := buildCipherStates(t, CipherAESGCM)
  11. roundtrip(t, NewCipherStateAESGCM(enc), NewCipherStateAESGCM(dec))
  12. }
  13. func TestCipherStateChaChaPolyRoundtrip(t *testing.T) {
  14. enc, dec := buildCipherStates(t, noise.CipherChaChaPoly)
  15. roundtrip(t, NewCipherStateChaChaPoly(enc), NewCipherStateChaChaPoly(dec))
  16. }
  17. func TestNewCipherStateDispatch(t *testing.T) {
  18. encA, _ := buildCipherStates(t, CipherAESGCM)
  19. encC, _ := buildCipherStates(t, noise.CipherChaChaPoly)
  20. assert.IsType(t, &CipherStateAESGCM{}, NewCipherState(encA, CipherAESGCM))
  21. assert.IsType(t, &CipherStateChaChaPoly{}, NewCipherState(encC, noise.CipherChaChaPoly))
  22. }
  23. func TestNewCipherStateUnsupportedPanics(t *testing.T) {
  24. enc, _ := buildCipherStates(t, CipherAESGCM)
  25. assert.Panics(t, func() {
  26. NewCipherState(enc, fakeCipher{})
  27. })
  28. }
  29. type fakeCipher struct{}
  30. func (fakeCipher) Cipher(k [32]byte) noise.Cipher { return nil }
  31. func (fakeCipher) CipherName() string { return "Fake" }
  32. // buildCipherStates runs an in-memory NN handshake with the requested cipher
  33. // to produce a pair of post-handshake CipherStates that share keys.
  34. func buildCipherStates(t *testing.T, c noise.CipherFunc) (*noise.CipherState, *noise.CipherState) {
  35. t.Helper()
  36. suite := noise.NewCipherSuite(noise.DH25519, c, noise.HashSHA256)
  37. cfg := noise.Config{CipherSuite: suite, Pattern: noise.HandshakeNN}
  38. cfg.Initiator = true
  39. hsI, err := noise.NewHandshakeState(cfg)
  40. require.NoError(t, err)
  41. cfg.Initiator = false
  42. hsR, err := noise.NewHandshakeState(cfg)
  43. require.NoError(t, err)
  44. msg, _, _, err := hsI.WriteMessage(nil, nil)
  45. require.NoError(t, err)
  46. _, _, _, err = hsR.ReadMessage(nil, msg)
  47. require.NoError(t, err)
  48. msg, dR, _, err := hsR.WriteMessage(nil, nil)
  49. require.NoError(t, err)
  50. _, eI, _, err := hsI.ReadMessage(nil, msg)
  51. require.NoError(t, err)
  52. require.NotNil(t, eI)
  53. require.NotNil(t, dR)
  54. // noise returns (cs1, cs2) where cs1 is the initiator->responder cipher.
  55. return eI, dR
  56. }
  57. func roundtrip(t *testing.T, enc, dec CipherState) {
  58. t.Helper()
  59. plaintext := []byte("nebula cipher state roundtrip")
  60. ad := []byte("aad")
  61. nb := make([]byte, 12)
  62. ct, err := enc.EncryptDanger(nil, ad, plaintext, 1, nb)
  63. require.NoError(t, err)
  64. assert.NotEqual(t, plaintext, ct)
  65. pt, err := dec.DecryptDanger(nil, ad, ct, 1, nb)
  66. require.NoError(t, err)
  67. assert.Equal(t, plaintext, pt)
  68. // Wrong nonce must fail authentication.
  69. _, err = dec.DecryptDanger(nil, ad, ct, 2, nb)
  70. require.Error(t, err)
  71. assert.Equal(t, enc.Overhead(), dec.Overhead())
  72. assert.Equal(t, 16, enc.Overhead())
  73. }
  74. func TestEncryptRejectsExhaustedCounter(t *testing.T) {
  75. // Pin the headroom below the uint64 wrap so a typo can't silently move the ceiling.
  76. require.Equal(t, uint64(1)<<40, RejectHeadroom)
  77. require.Equal(t, math.MaxUint64-RejectHeadroom, RejectAfterMessages)
  78. encA, _ := buildCipherStates(t, CipherAESGCM)
  79. encC, _ := buildCipherStates(t, noise.CipherChaChaPoly)
  80. nb := make([]byte, 12)
  81. for _, cs := range []CipherState{NewCipherStateAESGCM(encA), NewCipherStateChaChaPoly(encC)} {
  82. _, err := cs.EncryptDanger(nil, nil, []byte("x"), RejectAfterMessages-1, nb)
  83. require.NoError(t, err)
  84. _, err = cs.EncryptDanger(nil, nil, []byte("x"), RejectAfterMessages, nb)
  85. require.ErrorIs(t, err, ErrMessageCounterExhausted)
  86. }
  87. }
  88. func BenchmarkCipherStateEncryptAESGCM(b *testing.B) {
  89. enc, _ := buildCipherStatesB(b, CipherAESGCM)
  90. benchEncryptCipherState(b, NewCipherState(enc, CipherAESGCM))
  91. }
  92. func BenchmarkCipherStateEncryptChaChaPoly(b *testing.B) {
  93. enc, _ := buildCipherStatesB(b, noise.CipherChaChaPoly)
  94. benchEncryptCipherState(b, NewCipherState(enc, noise.CipherChaChaPoly))
  95. }
  96. func benchEncryptCipherState(b *testing.B, cs CipherState) {
  97. plaintext := make([]byte, 1280)
  98. ad := make([]byte, 16)
  99. nb := make([]byte, 12)
  100. out := make([]byte, 0, len(plaintext)+cs.Overhead())
  101. b.ResetTimer()
  102. b.ReportAllocs()
  103. for i := 0; i < b.N; i++ {
  104. var err error
  105. out, err = cs.EncryptDanger(out[:0], ad, plaintext, uint64(i+1), nb)
  106. if err != nil {
  107. b.Fatal(err)
  108. }
  109. }
  110. }
  111. func buildCipherStatesB(b *testing.B, c noise.CipherFunc) (*noise.CipherState, *noise.CipherState) {
  112. b.Helper()
  113. suite := noise.NewCipherSuite(noise.DH25519, c, noise.HashSHA256)
  114. cfg := noise.Config{CipherSuite: suite, Pattern: noise.HandshakeNN}
  115. cfg.Initiator = true
  116. hsI, err := noise.NewHandshakeState(cfg)
  117. if err != nil {
  118. b.Fatal(err)
  119. }
  120. cfg.Initiator = false
  121. hsR, err := noise.NewHandshakeState(cfg)
  122. if err != nil {
  123. b.Fatal(err)
  124. }
  125. msg, _, _, err := hsI.WriteMessage(nil, nil)
  126. if err != nil {
  127. b.Fatal(err)
  128. }
  129. if _, _, _, err := hsR.ReadMessage(nil, msg); err != nil {
  130. b.Fatal(err)
  131. }
  132. msg, dR, _, err := hsR.WriteMessage(nil, nil)
  133. if err != nil {
  134. b.Fatal(err)
  135. }
  136. _, eI, _, err := hsI.ReadMessage(nil, msg)
  137. if err != nil {
  138. b.Fatal(err)
  139. }
  140. return eI, dR
  141. }
  142. func TestCipherStateNilSafety(t *testing.T) {
  143. var aes *CipherStateAESGCM
  144. _, err := aes.EncryptDanger(nil, nil, nil, 0, make([]byte, 12))
  145. require.Error(t, err)
  146. out, err := aes.DecryptDanger(nil, nil, nil, 0, make([]byte, 12))
  147. require.NoError(t, err)
  148. assert.Empty(t, out)
  149. assert.Equal(t, 0, aes.Overhead())
  150. var cc *CipherStateChaChaPoly
  151. _, err = cc.EncryptDanger(nil, nil, nil, 0, make([]byte, 12))
  152. require.Error(t, err)
  153. out, err = cc.DecryptDanger(nil, nil, nil, 0, make([]byte, 12))
  154. require.NoError(t, err)
  155. assert.Empty(t, out)
  156. assert.Equal(t, 0, cc.Overhead())
  157. }