cipher_state.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. package noiseutil
  2. import (
  3. "crypto/cipher"
  4. "errors"
  5. "fmt"
  6. "math"
  7. "github.com/flynn/noise"
  8. )
  9. // RejectHeadroom is the wrap gap for senders racing the counter, sized large enough for any routine count.
  10. const RejectHeadroom = uint64(1) << 40
  11. // RejectAfterMessages is the nonce ceiling: encrypting stops RejectHeadroom short of the wrap.
  12. const RejectAfterMessages = math.MaxUint64 - RejectHeadroom
  13. // ErrMessageCounterExhausted is returned by EncryptDanger once the nonce reaches RejectAfterMessages.
  14. var ErrMessageCounterExhausted = errors.New("message counter exhausted")
  15. // CipherState is the post-handshake AEAD cipher used for the data plane.
  16. // Each supported cipher has its own concrete implementation in this package with the nonce endianness hardcoded,
  17. // so the encrypt/decrypt fast path avoids interface dispatch on the byte order.
  18. type CipherState interface {
  19. // EncryptDanger encrypts and authenticates a given payload.
  20. //
  21. // out is a destination slice to hold the output of the EncryptDanger operation.
  22. // - ad is additional data, which will be authenticated and appended to out, but not encrypted.
  23. // - plaintext is encrypted, authenticated and appended to out.
  24. // - n is a nonce value which must never be re-used with this key.
  25. // - nb is a scratch buffer used to assemble the nonce.
  26. EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error)
  27. // DecryptDanger authenticates and decrypts a given payload, with the same argument shape as EncryptDanger.
  28. DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error)
  29. // Overhead returns the AEAD tag size, or 0 if the receiver is nil.
  30. Overhead() int
  31. }
  32. // NewCipherState wraps the post-handshake noise.CipherState in the per-cipher type that matches cipherFunc.
  33. // cipherFunc must be the same cipher used to build the noise CipherSuite that produced s.
  34. func NewCipherState(s *noise.CipherState, cipherFunc noise.CipherFunc) CipherState {
  35. if cs, ok := s.Cipher().(CipherState); ok {
  36. return cs
  37. }
  38. switch cipherFunc.CipherName() {
  39. case noise.CipherAESGCM.CipherName():
  40. return NewCipherStateAESGCM(s)
  41. case noise.CipherChaChaPoly.CipherName():
  42. return NewCipherStateChaChaPoly(s)
  43. default:
  44. panic(fmt.Sprintf("noiseutil: unsupported cipher %q", cipherFunc.CipherName()))
  45. }
  46. }
  47. // NewCipherStateFromKey builds a data-plane CipherState directly from raw key
  48. // bytes, bypassing the noise handshake. Multiport lanes use this to key a
  49. // derived session off the base tunnel's negotiated keys; the caller owns the
  50. // guarantee that key is used with exactly one CipherState so nonces never
  51. // repeat.
  52. func NewCipherStateFromKey(key [32]byte, cipherFunc noise.CipherFunc) CipherState {
  53. c := cipherFunc.Cipher(key)
  54. if cs, ok := c.(CipherState); ok {
  55. return cs
  56. }
  57. aead, ok := c.(cipher.AEAD)
  58. if !ok {
  59. panic(fmt.Sprintf("noiseutil: cipher %q does not expose an AEAD", cipherFunc.CipherName()))
  60. }
  61. switch cipherFunc.CipherName() {
  62. case noise.CipherAESGCM.CipherName():
  63. return &CipherStateAESGCM{c: aead}
  64. case noise.CipherChaChaPoly.CipherName():
  65. return &CipherStateChaChaPoly{c: aead}
  66. default:
  67. panic(fmt.Sprintf("noiseutil: unsupported cipher %q", cipherFunc.CipherName()))
  68. }
  69. }