1
0

cipher_state_test.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package noiseutil
  2. import (
  3. "crypto/fips140"
  4. "math"
  5. "testing"
  6. "github.com/DefinedNet/noise"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. )
  10. func TestCipherStateAESGCMRoundtrip(t *testing.T) {
  11. enc, dec := buildCipherStates(t, CipherAESGCM)
  12. roundtrip(t, NewCipherState(enc, CipherAESGCM), NewCipherState(dec, CipherAESGCM))
  13. }
  14. func TestCipherStateChaChaPolyRoundtrip(t *testing.T) {
  15. enc, dec := buildCipherStates(t, noise.CipherChaChaPoly)
  16. roundtrip(t, NewCipherState(enc, noise.CipherChaChaPoly), NewCipherState(dec, noise.CipherChaChaPoly))
  17. }
  18. func TestNewCipherStateDispatch(t *testing.T) {
  19. encA, _ := buildCipherStates(t, CipherAESGCM)
  20. encC, _ := buildCipherStates(t, noise.CipherChaChaPoly)
  21. if !boringEnabled && !fips140.Enabled() {
  22. assert.IsType(t, &CipherStateAESGCM{}, NewCipherState(encA, CipherAESGCM))
  23. } else {
  24. // fips140
  25. assert.IsType(t, encA.Cipher(), NewCipherState(encA, CipherAESGCM))
  26. }
  27. assert.IsType(t, &CipherStateChaChaPoly{}, NewCipherState(encC, noise.CipherChaChaPoly))
  28. }
  29. func TestNewCipherStateUnsupportedPanics(t *testing.T) {
  30. enc, _ := buildCipherStates(t, noise.CipherChaChaPoly)
  31. assert.Panics(t, func() {
  32. NewCipherState(enc, fakeCipher{})
  33. })
  34. }
  35. type fakeCipher struct{}
  36. func (fakeCipher) Cipher(k [32]byte) noise.Cipher { return nil }
  37. func (fakeCipher) CipherName() string { return "Fake" }
  38. // buildCipherStates runs an in-memory NN handshake with the requested cipher
  39. // to produce a pair of post-handshake CipherStates that share keys.
  40. func buildCipherStates(t *testing.T, c noise.CipherFunc) (*noise.CipherState, *noise.CipherState) {
  41. t.Helper()
  42. suite := noise.NewCipherSuite(noise.DH25519, c, noise.HashSHA256)
  43. cfg := noise.Config{CipherSuite: suite, Pattern: noise.HandshakeNN}
  44. cfg.Initiator = true
  45. hsI, err := noise.NewHandshakeState(cfg)
  46. require.NoError(t, err)
  47. cfg.Initiator = false
  48. hsR, err := noise.NewHandshakeState(cfg)
  49. require.NoError(t, err)
  50. msg, _, _, err := hsI.WriteMessage(nil, nil)
  51. require.NoError(t, err)
  52. _, _, _, err = hsR.ReadMessage(nil, msg)
  53. require.NoError(t, err)
  54. msg, dR, _, err := hsR.WriteMessage(nil, nil)
  55. require.NoError(t, err)
  56. _, eI, _, err := hsI.ReadMessage(nil, msg)
  57. require.NoError(t, err)
  58. require.NotNil(t, eI)
  59. require.NotNil(t, dR)
  60. // noise returns (cs1, cs2) where cs1 is the initiator->responder cipher.
  61. return eI, dR
  62. }
  63. func roundtrip(t *testing.T, enc, dec CipherState) {
  64. t.Helper()
  65. plaintext := []byte("nebula cipher state roundtrip")
  66. ad := []byte("aad")
  67. nb := make([]byte, 12)
  68. ct, err := enc.EncryptDanger(nil, ad, plaintext, 1, nb)
  69. require.NoError(t, err)
  70. assert.NotEqual(t, plaintext, ct)
  71. pt, err := dec.DecryptDanger(nil, ad, ct, 1, nb)
  72. require.NoError(t, err)
  73. assert.Equal(t, plaintext, pt)
  74. // Wrong nonce must fail authentication.
  75. _, err = dec.DecryptDanger(nil, ad, ct, 2, nb)
  76. require.Error(t, err)
  77. assert.Equal(t, enc.Overhead(), dec.Overhead())
  78. assert.Equal(t, 16, enc.Overhead())
  79. }
  80. func TestEncryptRejectsExhaustedCounter(t *testing.T) {
  81. // Pin the headroom below the uint64 wrap so a typo can't silently move the ceiling.
  82. require.Equal(t, uint64(1)<<40, RejectHeadroom)
  83. require.Equal(t, math.MaxUint64-RejectHeadroom, RejectAfterMessages)
  84. encA, _ := buildCipherStates(t, CipherAESGCM)
  85. encC, _ := buildCipherStates(t, noise.CipherChaChaPoly)
  86. nb := make([]byte, 12)
  87. for _, cs := range []CipherState{NewCipherStateAESGCM(encA), NewCipherStateChaChaPoly(encC)} {
  88. _, err := cs.EncryptDanger(nil, nil, []byte("x"), RejectAfterMessages-1, nb)
  89. require.NoError(t, err)
  90. _, err = cs.EncryptDanger(nil, nil, []byte("x"), RejectAfterMessages, nb)
  91. require.ErrorIs(t, err, ErrMessageCounterExhausted)
  92. }
  93. }
  94. func BenchmarkCipherStateEncryptAESGCM(b *testing.B) {
  95. enc, _ := buildCipherStatesB(b, CipherAESGCM)
  96. benchEncryptCipherState(b, NewCipherState(enc, CipherAESGCM))
  97. }
  98. func BenchmarkCipherStateEncryptChaChaPoly(b *testing.B) {
  99. enc, _ := buildCipherStatesB(b, noise.CipherChaChaPoly)
  100. benchEncryptCipherState(b, NewCipherState(enc, noise.CipherChaChaPoly))
  101. }
  102. func benchEncryptCipherState(b *testing.B, cs CipherState) {
  103. plaintext := make([]byte, 1280)
  104. ad := make([]byte, 16)
  105. nb := make([]byte, 12)
  106. out := make([]byte, 0, len(plaintext)+cs.Overhead())
  107. b.ResetTimer()
  108. b.ReportAllocs()
  109. for i := 0; i < b.N; i++ {
  110. var err error
  111. out, err = cs.EncryptDanger(out[:0], ad, plaintext, uint64(i+1), nb)
  112. if err != nil {
  113. b.Fatal(err)
  114. }
  115. }
  116. }
  117. func buildCipherStatesB(b *testing.B, c noise.CipherFunc) (*noise.CipherState, *noise.CipherState) {
  118. b.Helper()
  119. suite := noise.NewCipherSuite(noise.DH25519, c, noise.HashSHA256)
  120. cfg := noise.Config{CipherSuite: suite, Pattern: noise.HandshakeNN}
  121. cfg.Initiator = true
  122. hsI, err := noise.NewHandshakeState(cfg)
  123. if err != nil {
  124. b.Fatal(err)
  125. }
  126. cfg.Initiator = false
  127. hsR, err := noise.NewHandshakeState(cfg)
  128. if err != nil {
  129. b.Fatal(err)
  130. }
  131. msg, _, _, err := hsI.WriteMessage(nil, nil)
  132. if err != nil {
  133. b.Fatal(err)
  134. }
  135. if _, _, _, err := hsR.ReadMessage(nil, msg); err != nil {
  136. b.Fatal(err)
  137. }
  138. msg, dR, _, err := hsR.WriteMessage(nil, nil)
  139. if err != nil {
  140. b.Fatal(err)
  141. }
  142. _, eI, _, err := hsI.ReadMessage(nil, msg)
  143. if err != nil {
  144. b.Fatal(err)
  145. }
  146. return eI, dR
  147. }
  148. func TestCipherStateNilSafety(t *testing.T) {
  149. var aes *CipherStateAESGCM
  150. _, err := aes.EncryptDanger(nil, nil, nil, 0, make([]byte, 12))
  151. require.Error(t, err)
  152. out, err := aes.DecryptDanger(nil, nil, nil, 0, make([]byte, 12))
  153. require.NoError(t, err)
  154. assert.Empty(t, out)
  155. assert.Equal(t, 0, aes.Overhead())
  156. var cc *CipherStateChaChaPoly
  157. _, err = cc.EncryptDanger(nil, nil, nil, 0, make([]byte, 12))
  158. require.Error(t, err)
  159. out, err = cc.DecryptDanger(nil, nil, nil, 0, make([]byte, 12))
  160. require.NoError(t, err)
  161. assert.Empty(t, out)
  162. assert.Equal(t, 0, cc.Overhead())
  163. }
  164. func TestCipherStateAESGCMInPlaceDecrypt(t *testing.T) {
  165. enc, dec := buildCipherStates(t, CipherAESGCM)
  166. inPlaceDecrypt(t, NewCipherStateAESGCM(enc), NewCipherStateAESGCM(dec))
  167. }
  168. func TestCipherStateChaChaPolyInPlaceDecrypt(t *testing.T) {
  169. enc, dec := buildCipherStates(t, noise.CipherChaChaPoly)
  170. inPlaceDecrypt(t, NewCipherStateChaChaPoly(enc), NewCipherStateChaChaPoly(dec))
  171. }
  172. func inPlaceDecrypt(t *testing.T, enc, dec CipherState) {
  173. t.Helper()
  174. const hdrLen = 16
  175. plaintext := []byte("in-place decrypt should replace the ciphertext bytes")
  176. nb := make([]byte, 12)
  177. // packet = [16-byte header | ciphertext+tag], like a nebula Message.
  178. packet := make([]byte, hdrLen, hdrLen+len(plaintext)+enc.Overhead())
  179. for i := range packet {
  180. packet[i] = byte(i)
  181. }
  182. packet, err := enc.EncryptDanger(packet, packet[:hdrLen], plaintext, 1, nb)
  183. require.NoError(t, err)
  184. // Simulate a GRO row: [packet | next segment]. A failed auth on packet
  185. // may zero packet's plaintext region but must not touch the header, the
  186. // tag, or the neighboring segment.
  187. neighbor := []byte("next coalesced segment, must stay intact")
  188. row := append(append([]byte(nil), packet...), neighbor...)
  189. tampered := row[:len(packet)]
  190. tampered[hdrLen] ^= 0x01
  191. _, err = dec.DecryptDanger(tampered[hdrLen:hdrLen], tampered[:hdrLen], tampered[hdrLen:], 1, nb)
  192. require.Error(t, err)
  193. assert.Equal(t, packet[:hdrLen], tampered[:hdrLen], "failed auth must not touch the header")
  194. assert.Equal(t, packet[len(packet)-dec.Overhead():], tampered[len(tampered)-dec.Overhead():],
  195. "failed auth must not touch the tag")
  196. assert.Equal(t, neighbor, row[len(packet):], "failed auth must not touch the next segment")
  197. out, err := dec.DecryptDanger(packet[hdrLen:hdrLen], packet[:hdrLen], packet[hdrLen:], 1, nb)
  198. require.NoError(t, err)
  199. assert.Equal(t, plaintext, out)
  200. // The plaintext must be IN the packet buffer, not a fresh allocation.
  201. assert.Equal(t, &packet[hdrLen], &out[0], "plaintext must alias the packet buffer")
  202. }