gcm.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
  2. // SPDX-License-Identifier: MIT
  3. package ciphersuite
  4. import (
  5. "crypto/aes"
  6. "crypto/cipher"
  7. "encoding/binary"
  8. "fmt"
  9. "github.com/pion/dtls/v2/pkg/protocol"
  10. "github.com/pion/dtls/v2/pkg/protocol/recordlayer"
  11. )
  12. const (
  13. gcmTagLength = 16
  14. gcmNonceLength = 12
  15. )
  16. // GCM Provides an API to Encrypt/Decrypt DTLS 1.2 Packets
  17. type GCM struct {
  18. localGCM, remoteGCM cipher.AEAD
  19. localWriteIV, remoteWriteIV []byte
  20. }
  21. // NewGCM creates a DTLS GCM Cipher
  22. func NewGCM(localKey, localWriteIV, remoteKey, remoteWriteIV []byte) (*GCM, error) {
  23. localBlock, err := aes.NewCipher(localKey)
  24. if err != nil {
  25. return nil, err
  26. }
  27. localGCM, err := cipher.NewGCM(localBlock)
  28. if err != nil {
  29. return nil, err
  30. }
  31. remoteBlock, err := aes.NewCipher(remoteKey)
  32. if err != nil {
  33. return nil, err
  34. }
  35. remoteGCM, err := cipher.NewGCM(remoteBlock)
  36. if err != nil {
  37. return nil, err
  38. }
  39. return &GCM{
  40. localGCM: localGCM,
  41. localWriteIV: localWriteIV,
  42. remoteGCM: remoteGCM,
  43. remoteWriteIV: remoteWriteIV,
  44. }, nil
  45. }
  46. // Encrypt encrypt a DTLS RecordLayer message
  47. func (g *GCM) Encrypt(pkt *recordlayer.RecordLayer, raw []byte) ([]byte, error) {
  48. payload := raw[recordlayer.HeaderSize:]
  49. raw = raw[:recordlayer.HeaderSize]
  50. // [Psiphon]
  51. // See comment in generateAEADNonce.
  52. nonce := generateAEADNonce(g.localWriteIV, &pkt.Header)
  53. additionalData := generateAEADAdditionalData(&pkt.Header, len(payload))
  54. encryptedPayload := g.localGCM.Seal(nil, nonce, payload, additionalData)
  55. r := make([]byte, len(raw)+len(nonce[4:])+len(encryptedPayload))
  56. copy(r, raw)
  57. copy(r[len(raw):], nonce[4:])
  58. copy(r[len(raw)+len(nonce[4:]):], encryptedPayload)
  59. // Update recordLayer size to include explicit nonce
  60. binary.BigEndian.PutUint16(r[recordlayer.HeaderSize-2:], uint16(len(r)-recordlayer.HeaderSize))
  61. return r, nil
  62. }
  63. // Decrypt decrypts a DTLS RecordLayer message
  64. func (g *GCM) Decrypt(in []byte) ([]byte, error) {
  65. var h recordlayer.Header
  66. err := h.Unmarshal(in)
  67. switch {
  68. case err != nil:
  69. return nil, err
  70. case h.ContentType == protocol.ContentTypeChangeCipherSpec:
  71. // Nothing to encrypt with ChangeCipherSpec
  72. return in, nil
  73. case len(in) <= (8 + recordlayer.HeaderSize):
  74. return nil, errNotEnoughRoomForNonce
  75. }
  76. nonce := make([]byte, 0, gcmNonceLength)
  77. nonce = append(append(nonce, g.remoteWriteIV[:4]...), in[recordlayer.HeaderSize:recordlayer.HeaderSize+8]...)
  78. out := in[recordlayer.HeaderSize+8:]
  79. additionalData := generateAEADAdditionalData(&h, len(out)-gcmTagLength)
  80. out, err = g.remoteGCM.Open(out[:0], nonce, out, additionalData)
  81. if err != nil {
  82. return nil, fmt.Errorf("%w: %v", errDecryptPacket, err) //nolint:errorlint
  83. }
  84. return append(in[:recordlayer.HeaderSize], out...), nil
  85. }