github.com/sagernet/quic-go@v0.43.1-beta.1/internal/handshake_ech/header_protector.go (about)

     1  package handshake
     2  
     3  import (
     4  	"crypto/aes"
     5  	"crypto/cipher"
     6  	"encoding/binary"
     7  	"fmt"
     8  
     9  	"github.com/sagernet/quic-go/internal/protocol"
    10  	"github.com/sagernet/cloudflare-tls"
    11  	"golang.org/x/crypto/chacha20"
    12  )
    13  
    14  type headerProtector interface {
    15  	EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte)
    16  	DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte)
    17  }
    18  
    19  func hkdfHeaderProtectionLabel(v protocol.Version) string {
    20  	if v == protocol.Version2 {
    21  		return "quicv2 hp"
    22  	}
    23  	return "quic hp"
    24  }
    25  
    26  func newHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, v protocol.Version) headerProtector {
    27  	hkdfLabel := hkdfHeaderProtectionLabel(v)
    28  	switch suite.ID {
    29  	case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384:
    30  		return newAESHeaderProtector(suite, trafficSecret, isLongHeader, hkdfLabel)
    31  	case tls.TLS_CHACHA20_POLY1305_SHA256:
    32  		return newChaChaHeaderProtector(suite, trafficSecret, isLongHeader, hkdfLabel)
    33  	default:
    34  		panic(fmt.Sprintf("Invalid cipher suite id: %d", suite.ID))
    35  	}
    36  }
    37  
    38  type aesHeaderProtector struct {
    39  	mask         [16]byte // AES always has a 16 byte block size
    40  	block        cipher.Block
    41  	isLongHeader bool
    42  }
    43  
    44  var _ headerProtector = &aesHeaderProtector{}
    45  
    46  func newAESHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector {
    47  	hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen)
    48  	block, err := aes.NewCipher(hpKey)
    49  	if err != nil {
    50  		panic(fmt.Sprintf("error creating new AES cipher: %s", err))
    51  	}
    52  	return &aesHeaderProtector{
    53  		block:        block,
    54  		isLongHeader: isLongHeader,
    55  	}
    56  }
    57  
    58  func (p *aesHeaderProtector) DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
    59  	p.apply(sample, firstByte, hdrBytes)
    60  }
    61  
    62  func (p *aesHeaderProtector) EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
    63  	p.apply(sample, firstByte, hdrBytes)
    64  }
    65  
    66  func (p *aesHeaderProtector) apply(sample []byte, firstByte *byte, hdrBytes []byte) {
    67  	if len(sample) != len(p.mask) {
    68  		panic("invalid sample size")
    69  	}
    70  	p.block.Encrypt(p.mask[:], sample)
    71  	if p.isLongHeader {
    72  		*firstByte ^= p.mask[0] & 0xf
    73  	} else {
    74  		*firstByte ^= p.mask[0] & 0x1f
    75  	}
    76  	for i := range hdrBytes {
    77  		hdrBytes[i] ^= p.mask[i+1]
    78  	}
    79  }
    80  
    81  type chachaHeaderProtector struct {
    82  	mask [5]byte
    83  
    84  	key          [32]byte
    85  	isLongHeader bool
    86  }
    87  
    88  var _ headerProtector = &chachaHeaderProtector{}
    89  
    90  func newChaChaHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector {
    91  	hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen)
    92  
    93  	p := &chachaHeaderProtector{
    94  		isLongHeader: isLongHeader,
    95  	}
    96  	copy(p.key[:], hpKey)
    97  	return p
    98  }
    99  
   100  func (p *chachaHeaderProtector) DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
   101  	p.apply(sample, firstByte, hdrBytes)
   102  }
   103  
   104  func (p *chachaHeaderProtector) EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) {
   105  	p.apply(sample, firstByte, hdrBytes)
   106  }
   107  
   108  func (p *chachaHeaderProtector) apply(sample []byte, firstByte *byte, hdrBytes []byte) {
   109  	if len(sample) != 16 {
   110  		panic("invalid sample size")
   111  	}
   112  	for i := 0; i < 5; i++ {
   113  		p.mask[i] = 0
   114  	}
   115  	cipher, err := chacha20.NewUnauthenticatedCipher(p.key[:], sample[4:])
   116  	if err != nil {
   117  		panic(err)
   118  	}
   119  	cipher.SetCounter(binary.LittleEndian.Uint32(sample[:4]))
   120  	cipher.XORKeyStream(p.mask[:], p.mask[:])
   121  	p.applyMask(firstByte, hdrBytes)
   122  }
   123  
   124  func (p *chachaHeaderProtector) applyMask(firstByte *byte, hdrBytes []byte) {
   125  	if p.isLongHeader {
   126  		*firstByte ^= p.mask[0] & 0xf
   127  	} else {
   128  		*firstByte ^= p.mask[0] & 0x1f
   129  	}
   130  	for i := range hdrBytes {
   131  		hdrBytes[i] ^= p.mask[i+1]
   132  	}
   133  }