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 }