github.com/danielpfeifer02/quic-go-prio-packs@v0.41.0-28/internal/handshake/header_protector.go (about) 1 package handshake 2 3 import ( 4 "crypto/aes" 5 "crypto/cipher" 6 "crypto/tls" 7 "encoding/binary" 8 "fmt" 9 10 "golang.org/x/crypto/chacha20" 11 12 "github.com/danielpfeifer02/quic-go-prio-packs/crypto_turnoff" 13 "github.com/danielpfeifer02/quic-go-prio-packs/internal/protocol" 14 ) 15 16 type headerProtector interface { 17 EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) 18 DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) 19 } 20 21 func hkdfHeaderProtectionLabel(v protocol.Version) string { 22 if v == protocol.Version2 { 23 return "quicv2 hp" 24 } 25 return "quic hp" 26 } 27 28 func newHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, v protocol.Version) headerProtector { 29 hkdfLabel := hkdfHeaderProtectionLabel(v) 30 switch suite.ID { 31 case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384: 32 return newAESHeaderProtector(suite, trafficSecret, isLongHeader, hkdfLabel) 33 case tls.TLS_CHACHA20_POLY1305_SHA256: 34 return newChaChaHeaderProtector(suite, trafficSecret, isLongHeader, hkdfLabel) 35 36 // NO_CRYPTO_TAG 37 // based on https://pkg.go.dev/crypto/tls#pkg-constants 0x0000 is not used for any other cipher suite 38 case 0x0000: 39 return newNoHeaderProtector() 40 41 default: 42 panic(fmt.Sprintf("Invalid cipher suite id: %d", suite.ID)) 43 } 44 } 45 46 type aesHeaderProtector struct { 47 mask [16]byte // AES always has a 16 byte block size 48 block cipher.Block 49 isLongHeader bool 50 } 51 52 var _ headerProtector = &aesHeaderProtector{} 53 54 func newAESHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector { 55 hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen) 56 block, err := aes.NewCipher(hpKey) 57 if err != nil { 58 panic(fmt.Sprintf("error creating new AES cipher: %s", err)) 59 } 60 return &aesHeaderProtector{ 61 block: block, 62 isLongHeader: isLongHeader, 63 } 64 } 65 66 func (p *aesHeaderProtector) DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) { 67 // NO_CRYPTO_TAG 68 if crypto_turnoff.CRYPTO_TURNED_OFF { 69 return 70 } 71 p.apply(sample, firstByte, hdrBytes) 72 } 73 74 func (p *aesHeaderProtector) EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) { 75 // NO_CRYPTO_TAG 76 if crypto_turnoff.CRYPTO_TURNED_OFF { 77 return 78 } 79 p.apply(sample, firstByte, hdrBytes) 80 } 81 82 func (p *aesHeaderProtector) apply(sample []byte, firstByte *byte, hdrBytes []byte) { 83 if len(sample) != len(p.mask) { 84 panic("invalid sample size") 85 } 86 p.block.Encrypt(p.mask[:], sample) 87 if p.isLongHeader { 88 *firstByte ^= p.mask[0] & 0xf 89 } else { 90 *firstByte ^= p.mask[0] & 0x1f 91 } 92 for i := range hdrBytes { 93 hdrBytes[i] ^= p.mask[i+1] 94 } 95 } 96 97 type chachaHeaderProtector struct { 98 mask [5]byte 99 100 key [32]byte 101 isLongHeader bool 102 } 103 104 var _ headerProtector = &chachaHeaderProtector{} 105 106 func newChaChaHeaderProtector(suite *cipherSuite, trafficSecret []byte, isLongHeader bool, hkdfLabel string) headerProtector { 107 hpKey := hkdfExpandLabel(suite.Hash, trafficSecret, []byte{}, hkdfLabel, suite.KeyLen) 108 109 p := &chachaHeaderProtector{ 110 isLongHeader: isLongHeader, 111 } 112 copy(p.key[:], hpKey) 113 return p 114 } 115 116 func (p *chachaHeaderProtector) DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) { 117 // NO_CRYPTO_TAG 118 if crypto_turnoff.CRYPTO_TURNED_OFF { 119 return 120 } 121 p.apply(sample, firstByte, hdrBytes) 122 } 123 124 func (p *chachaHeaderProtector) EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) { 125 // NO_CRYPTO_TAG 126 if crypto_turnoff.CRYPTO_TURNED_OFF { 127 return 128 } 129 p.apply(sample, firstByte, hdrBytes) 130 } 131 132 func (p *chachaHeaderProtector) apply(sample []byte, firstByte *byte, hdrBytes []byte) { 133 if len(sample) != 16 { 134 panic("invalid sample size") 135 } 136 for i := 0; i < 5; i++ { 137 p.mask[i] = 0 138 } 139 cipher, err := chacha20.NewUnauthenticatedCipher(p.key[:], sample[4:]) 140 if err != nil { 141 panic(err) 142 } 143 cipher.SetCounter(binary.LittleEndian.Uint32(sample[:4])) 144 cipher.XORKeyStream(p.mask[:], p.mask[:]) 145 p.applyMask(firstByte, hdrBytes) 146 } 147 148 func (p *chachaHeaderProtector) applyMask(firstByte *byte, hdrBytes []byte) { 149 if p.isLongHeader { 150 *firstByte ^= p.mask[0] & 0xf 151 } else { 152 *firstByte ^= p.mask[0] & 0x1f 153 } 154 for i := range hdrBytes { 155 hdrBytes[i] ^= p.mask[i+1] 156 } 157 } 158 159 // NO_CRYPTO_TAG 160 // noHeaderProtector type to omit the header protection. 161 // TODOME even necessary? 162 163 type noHeaderProtector struct{} 164 165 var _ headerProtector = &noHeaderProtector{} 166 167 func newNoHeaderProtector() headerProtector { 168 p := &chachaHeaderProtector{} 169 return p 170 } 171 172 func (p *noHeaderProtector) DecryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) { 173 p.apply(sample, firstByte, hdrBytes) 174 } 175 176 func (p *noHeaderProtector) EncryptHeader(sample []byte, firstByte *byte, hdrBytes []byte) { 177 p.apply(sample, firstByte, hdrBytes) 178 } 179 180 func (p *noHeaderProtector) apply(sample []byte, firstByte *byte, hdrBytes []byte) {}