mirror of
https://github.com/apernet/OpenGFW.git
synced 2024-12-24 01:49:22 +08:00
194 lines
6.0 KiB
Go
194 lines
6.0 KiB
Go
|
package quic
|
||
|
|
||
|
import (
|
||
|
"crypto"
|
||
|
"crypto/aes"
|
||
|
"crypto/cipher"
|
||
|
"crypto/sha256"
|
||
|
"crypto/tls"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"hash"
|
||
|
|
||
|
"golang.org/x/crypto/chacha20"
|
||
|
"golang.org/x/crypto/chacha20poly1305"
|
||
|
"golang.org/x/crypto/cryptobyte"
|
||
|
"golang.org/x/crypto/hkdf"
|
||
|
)
|
||
|
|
||
|
// NewProtectionKey creates a new ProtectionKey.
|
||
|
func NewProtectionKey(suite uint16, secret []byte, v uint32) (*ProtectionKey, error) {
|
||
|
return newProtectionKey(suite, secret, v)
|
||
|
}
|
||
|
|
||
|
// NewInitialProtectionKey is like NewProtectionKey, but the returned protection key
|
||
|
// is used for encrypt/decrypt Initial Packet only.
|
||
|
//
|
||
|
// See: https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#name-initial-secrets
|
||
|
func NewInitialProtectionKey(secret []byte, v uint32) (*ProtectionKey, error) {
|
||
|
return NewProtectionKey(tls.TLS_AES_128_GCM_SHA256, secret, v)
|
||
|
}
|
||
|
|
||
|
// NewPacketProtector creates a new PacketProtector.
|
||
|
func NewPacketProtector(key *ProtectionKey) *PacketProtector {
|
||
|
return &PacketProtector{key: key}
|
||
|
}
|
||
|
|
||
|
// PacketProtector is used for protecting a QUIC packet.
|
||
|
//
|
||
|
// See: https://www.rfc-editor.org/rfc/rfc9001.html#name-packet-protection
|
||
|
type PacketProtector struct {
|
||
|
key *ProtectionKey
|
||
|
}
|
||
|
|
||
|
// UnProtect decrypts a QUIC packet.
|
||
|
func (pp *PacketProtector) UnProtect(packet []byte, pnOffset, pnMax int64) ([]byte, error) {
|
||
|
if isLongHeader(packet[0]) && int64(len(packet)) < pnOffset+4+16 {
|
||
|
return nil, errors.New("packet with long header is too small")
|
||
|
}
|
||
|
|
||
|
// https://www.rfc-editor.org/rfc/rfc9001.html#name-header-protection-sample
|
||
|
sampleOffset := pnOffset + 4
|
||
|
sample := packet[sampleOffset : sampleOffset+16]
|
||
|
|
||
|
// https://www.rfc-editor.org/rfc/rfc9001.html#name-header-protection-applicati
|
||
|
mask := pp.key.headerProtection(sample)
|
||
|
if isLongHeader(packet[0]) {
|
||
|
// Long header: 4 bits masked
|
||
|
packet[0] ^= mask[0] & 0x0f
|
||
|
} else {
|
||
|
// Short header: 5 bits masked
|
||
|
packet[0] ^= mask[0] & 0x1f
|
||
|
}
|
||
|
|
||
|
pnLen := packet[0]&0x3 + 1
|
||
|
pn := int64(0)
|
||
|
for i := uint8(0); i < pnLen; i++ {
|
||
|
packet[pnOffset:][i] ^= mask[1+i]
|
||
|
pn = (pn << 8) | int64(packet[pnOffset:][i])
|
||
|
}
|
||
|
pn = decodePacketNumber(pnMax, pn, pnLen)
|
||
|
hdr := packet[:pnOffset+int64(pnLen)]
|
||
|
payload := packet[pnOffset:][pnLen:]
|
||
|
dec, err := pp.key.aead.Open(payload[:0], pp.key.nonce(pn), payload, hdr)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("decryption failed: %w", err)
|
||
|
}
|
||
|
return dec, nil
|
||
|
}
|
||
|
|
||
|
// ProtectionKey is the key used to protect a QUIC packet.
|
||
|
type ProtectionKey struct {
|
||
|
aead cipher.AEAD
|
||
|
headerProtection func(sample []byte) (mask []byte)
|
||
|
iv []byte
|
||
|
}
|
||
|
|
||
|
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#name-aead-usage
|
||
|
//
|
||
|
// "The 62 bits of the reconstructed QUIC packet number in network byte order are
|
||
|
// left-padded with zeros to the size of the IV. The exclusive OR of the padded
|
||
|
// packet number and the IV forms the AEAD nonce."
|
||
|
func (pk *ProtectionKey) nonce(pn int64) []byte {
|
||
|
nonce := make([]byte, len(pk.iv))
|
||
|
binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(pn))
|
||
|
for i := range pk.iv {
|
||
|
nonce[i] ^= pk.iv[i]
|
||
|
}
|
||
|
return nonce
|
||
|
}
|
||
|
|
||
|
func newProtectionKey(suite uint16, secret []byte, v uint32) (*ProtectionKey, error) {
|
||
|
switch suite {
|
||
|
case tls.TLS_AES_128_GCM_SHA256:
|
||
|
key := hkdfExpandLabel(crypto.SHA256.New, secret, keyLabel(v), nil, 16)
|
||
|
c, err := aes.NewCipher(key)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
aead, err := cipher.NewGCM(c)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
iv := hkdfExpandLabel(crypto.SHA256.New, secret, ivLabel(v), nil, aead.NonceSize())
|
||
|
hpKey := hkdfExpandLabel(crypto.SHA256.New, secret, headerProtectionLabel(v), nil, 16)
|
||
|
hp, err := aes.NewCipher(hpKey)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
k := &ProtectionKey{}
|
||
|
k.aead = aead
|
||
|
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#name-aes-based-header-protection
|
||
|
k.headerProtection = func(sample []byte) []byte {
|
||
|
mask := make([]byte, hp.BlockSize())
|
||
|
hp.Encrypt(mask, sample)
|
||
|
return mask
|
||
|
}
|
||
|
k.iv = iv
|
||
|
return k, nil
|
||
|
case tls.TLS_CHACHA20_POLY1305_SHA256:
|
||
|
key := hkdfExpandLabel(crypto.SHA256.New, secret, keyLabel(v), nil, chacha20poly1305.KeySize)
|
||
|
aead, err := chacha20poly1305.New(key)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
iv := hkdfExpandLabel(crypto.SHA256.New, secret, ivLabel(v), nil, aead.NonceSize())
|
||
|
hpKey := hkdfExpandLabel(sha256.New, secret, headerProtectionLabel(v), nil, chacha20.KeySize)
|
||
|
k := &ProtectionKey{}
|
||
|
k.aead = aead
|
||
|
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#name-chacha20-based-header-prote
|
||
|
k.headerProtection = func(sample []byte) []byte {
|
||
|
nonce := sample[4:16]
|
||
|
c, err := chacha20.NewUnauthenticatedCipher(hpKey, nonce)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
c.SetCounter(binary.LittleEndian.Uint32(sample[:4]))
|
||
|
mask := make([]byte, 5)
|
||
|
c.XORKeyStream(mask, mask)
|
||
|
return mask
|
||
|
}
|
||
|
k.iv = iv
|
||
|
return k, nil
|
||
|
}
|
||
|
return nil, errors.New("not supported cipher suite")
|
||
|
}
|
||
|
|
||
|
// decodePacketNumber decode the packet number after header protection removed.
|
||
|
//
|
||
|
// See: https://datatracker.ietf.org/doc/html/draft-ietf-quic-transport-32#section-appendix.a
|
||
|
func decodePacketNumber(largest, truncated int64, nbits uint8) int64 {
|
||
|
expected := largest + 1
|
||
|
win := int64(1 << (nbits * 8))
|
||
|
hwin := win / 2
|
||
|
mask := win - 1
|
||
|
candidate := (expected &^ mask) | truncated
|
||
|
switch {
|
||
|
case candidate <= expected-hwin && candidate < (1<<62)-win:
|
||
|
return candidate + win
|
||
|
case candidate > expected+hwin && candidate >= win:
|
||
|
return candidate - win
|
||
|
}
|
||
|
return candidate
|
||
|
}
|
||
|
|
||
|
// Copied from crypto/tls/key_schedule.go.
|
||
|
func hkdfExpandLabel(hash func() hash.Hash, secret []byte, label string, context []byte, length int) []byte {
|
||
|
var hkdfLabel cryptobyte.Builder
|
||
|
hkdfLabel.AddUint16(uint16(length))
|
||
|
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||
|
b.AddBytes([]byte("tls13 "))
|
||
|
b.AddBytes([]byte(label))
|
||
|
})
|
||
|
hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||
|
b.AddBytes(context)
|
||
|
})
|
||
|
out := make([]byte, length)
|
||
|
n, err := hkdf.Expand(hash, secret, hkdfLabel.BytesOrPanic()).Read(out)
|
||
|
if err != nil || n != length {
|
||
|
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
||
|
}
|
||
|
return out
|
||
|
}
|