Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions chlo.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type CHLO struct {
DataLength uint16
Tag string
TagNumber uint16
TagValues map[string]string
TagValues map[string]string
TagsInOrder []string
}

Expand All @@ -49,20 +49,23 @@ func (ch CHLO) String() string {
}

func (ch *CHLO) DecodeCHLO(payload []byte) error {
if string(payload[1:5]) == string([]byte{0xff, 0x00, 0x00, 0x1c}) {
return ch.decodeIETF(payload[:])
}
ch.Raw = payload
// Only process CHLO packets
if !(bytes.Contains(payload, []byte("CHLO"))) {
return ErrWrongType
}
// Public Flags
ch.PublicFlags = payload[0]
ch.PfVersion = payload[0]&0x01 != 0 // Version
ch.PfReset = payload[0]&0x02 != 0 // Reset
ch.PfDivNonce = payload[0]&0x04 != 0 // Diversification Nonce
ch.PfCIDLen = payload[0]&0x08 != 0 // CID Length
ch.PfPacketNumLen = (payload[0]&0x30 >> 4) + 1 // Packet Number Length in bytes
ch.PfMultipath = payload[0]&0x40 != 0 // Multipath
ch.PfReserved = payload[0]&0x80 != 0 // Reserved
ch.PfVersion = payload[0]&0x01 != 0 // Version
ch.PfReset = payload[0]&0x02 != 0 // Reset
ch.PfDivNonce = payload[0]&0x04 != 0 // Diversification Nonce
ch.PfCIDLen = payload[0]&0x08 != 0 // CID Length
ch.PfPacketNumLen = (payload[0] & 0x30 >> 4) + 1 // Packet Number Length in bytes
ch.PfMultipath = payload[0]&0x40 != 0 // Multipath
ch.PfReserved = payload[0]&0x80 != 0 // Reserved
if ch.PublicFlags == 0 {
return ErrBadPFlags
}
Expand All @@ -84,7 +87,7 @@ func (ch *CHLO) DecodeCHLO(payload []byte) error {
case 2:
ch.PacketNumber = uint(binary.BigEndian.Uint16(hs[0:2]))
case 3:
ch.PacketNumber = (uint(hs[0])<<16) | (uint(hs[1])<<8) | uint(hs[2])
ch.PacketNumber = (uint(hs[0]) << 16) | (uint(hs[1]) << 8) | uint(hs[2])
}
hs = hs[ch.PfPacketNumLen:]
// Message Authentication Hash
Expand All @@ -93,9 +96,9 @@ func (ch *CHLO) DecodeCHLO(payload []byte) error {
ch.FrameType = hs[12]
ch.FtStream = hs[12]&0x80 != 0 // STREAM
ch.FtFIN = hs[12]&0x40 != 0 // FIN
ch.FtDataLength = (hs[12]&0x20 >> 5) + 1 // Data Length in bytes
ch.FtOffsetLength = hs[12]&0x1C >> 2 // Offset Length
ch.FtStreamLength = hs[12]&0x3 // Stream Length
ch.FtDataLength = (hs[12] & 0x20 >> 5) + 1 // Data Length in bytes
ch.FtOffsetLength = hs[12] & 0x1C >> 2 // Offset Length
ch.FtStreamLength = hs[12] & 0x3 // Stream Length
ch.StreamID = uint8(hs[13]) // Stream ID
// Data Length
if ch.FtDataLength == 2 {
Expand All @@ -113,7 +116,7 @@ func (ch *CHLO) DecodeCHLO(payload []byte) error {
}
// Tag Number
ch.TagNumber = binary.LittleEndian.Uint16(hs[20:22])
hs = hs[24:] // Padding: 0000
hs = hs[24:] // Padding: 0000
if len(hs) < 2 {
return ErrBadLength
}
Expand Down Expand Up @@ -145,7 +148,7 @@ func (ch *CHLO) DecodeCHLO(payload []byte) error {
switch tag {
case "SNI", "UAID", "AEAD", "KEXS", "VER", "PDMD", "COPT":
ch.TagValues[tag] = string(hs[0:TagLen])
case "PAD": //do nothing
case "PAD": //do nothing
default:
ch.TagValues[tag] = hex.EncodeToString(hs[0:TagLen])
}
Expand Down
186 changes: 186 additions & 0 deletions ietf-28.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package quick

import (
"crypto/aes"
"crypto/sha256"
"encoding/binary"
"fmt"

"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/hkdf"

"github.com/marten-seemann/qtls"
)

var salt = []byte{
0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a,
0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65,
0xbe, 0xf9, 0xf5, 0x02,
}

func (c *CHLO) decodeIETF(p []byte) error {
defer func() {
if r := recover(); r != nil {
// print("maloformed err")
}
}()
c.Raw = p[:]
c.TagValues = make(map[string]string)
i := 0
sampleOffset := 7
c.Version = fmt.Sprintf("%x", p[1:5])
i += 5
dcil, l := varint(p[i:])
i += l
sampleOffset += int(dcil)
dcid := make([]byte, dcil)
copy(dcid, p[i:])
c.CID = dcid
i += int(dcil)
scil, l := varint(p[i:])
i += l
sampleOffset += int(scil)
scid := make([]byte, scil)
copy(scid, p[i:])
i += int(scil)
tl, l := varint(p[i:])
sampleOffset += l + int(tl)
i += l
// token neccessary ??
// token := make([]byte, int(tl))
// copy(token, p[i:i+int(tl)])
// fmt.Printf("token %x\n", token)
i += int(tl)
_, l = varint(p[i:])
sampleOffset += l + 4
i += l
sample := p[sampleOffset : sampleOffset+16]
initialSecret := hkdf.Extract(sha256.New, dcid, salt)
clientSc := hkdfExpandLabel([]byte{}, []byte("client in"), initialSecret, 32)
headerPr := hkdfExpandLabel([]byte{}, []byte("quic hp"), clientSc, 16)
block, err := aes.NewCipher(headerPr)
if err != nil {
return err
}
mask := make([]byte, block.BlockSize())
block.Encrypt(mask, sample)
_pnlen := int((p[0] ^ mask[0]) & 0x0f)
_pn := make([]byte, 4)
for m := 0; m <= _pnlen; m++ {
_pn[m] = p[sampleOffset-4+m] ^ mask[1+m]
}
pn := binary.BigEndian.Uint32(_pn)
c.PacketNumber = uint(pn)
iv := hkdfExpandLabel([]byte{}, []byte("quic iv"), clientSc, 12)
key := hkdfExpandLabel([]byte{}, []byte("quic key"), clientSc, 16)
aead := qtls.AEADAESGCMTLS13(key, iv)
nonceBuf := make([]byte, aead.NonceSize())
binary.BigEndian.PutUint64(nonceBuf[len(nonceBuf)-8:], uint64(pn))
dec := aead.Seal(nil, nonceBuf, p[sampleOffset-(3-_pnlen):], p[:sampleOffset])
c.decodeCryptoFrame(dec[:len(p[sampleOffset-(3-_pnlen):])-16])
return nil
}

func hkdfExpandLabel(context, label, secret []byte, size int) []byte {
cb := cryptobyte.Builder{}
cb.AddUint16(uint16(size))
cb.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes([]byte("tls13 "))
b.AddBytes([]byte(label))
})
cb.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes(context)
})
out := make([]byte, size)
_, _ = hkdf.Expand(sha256.New, secret, cb.BytesOrPanic()).Read(out)
return out
}

// ATQ draft-27

func varint(p []byte) (uint64, int) {
n := p[0]
l := uint8(n >> 6)
switch l {
case 0x00:
return uint64(n & 0x3f), 1
case 0x01:
n2 := p[1]
return uint64(binary.BigEndian.Uint16([]byte{n & 0x3f, n2})), 2
case 0x02:
n2 := []byte{p[1], p[2]}
return uint64(binary.BigEndian.Uint32([]byte{n & 0x3f, n2[0], n2[1]})), 3
case 0x03:
n2 := []byte{p[1], p[2], p[3]}
return binary.BigEndian.Uint64([]byte{n & 0x3f, n2[0], n2[1], n2[2]}), 4
}
return 0, 0
}

func (c *CHLO) decodeTLSHandshake(p []byte) {
if p[0] == 0x01 {
c.TagValues["TLS min version"] = fmt.Sprintf("%x", p[4])
c.TagValues["TLS max version"] = fmt.Sprintf("%x", p[5])
_, sidlen := varint(p[6+32:])
l := binary.BigEndian.Uint16([]byte{p[6+32+sidlen], p[6+32+sidlen+1]})
i := 6 + 32 + sidlen + 1
for k := 0; k < int(l); k += 2 {
c.TagsInOrder = append(c.TagsInOrder, nameCipher(p[i+k+1], p[i+k+2]))
}
i += int(l) + 1
i += 2
extlen := binary.BigEndian.Uint16([]byte{p[i], p[1+i]})
i += 2

for k := i; k < i+int(extlen); k += 0 {
ty := binary.BigEndian.Uint16([]byte{p[k], p[1+k]})
k += 2
l := binary.BigEndian.Uint16([]byte{p[k], p[1+k]})
k += 2
if ty == 0x00 {
t := k + 2
if p[t] == 0x00 {
c.TagValues["SNI"] = string(p[t+3 : k+int(l)])
}
}
k += int(l)
}
}
}

func nameCipher(f, s byte) string {
switch f {
case 0x13:
switch s {
case 0x01:
return "TLS_AES_128_GCM_SHA384"
case 0x02:
return "TLS_AES_256_GCM_SHA256"
case 0x03:
return "TLS_CHACHA20_POLY1305_SHA256"
case 0x04:
return "TLS_AES_128_CCM_SHA256"
case 0x05:
return "TLS_AES_128_CCM_8_SHA256"
default:
return fmt.Sprintf("urecognized id %x", s)
}
default:
return "invaild value"
}
}

func (c *CHLO) decodeCryptoFrame(p []byte) {
size := len(p)
i := 0
for i < size {
if p[i] == 0x06 {
offset, l := varint(p[i+1:])
i += l
length, l := varint(p[i+1:])
c.decodeTLSHandshake(p[i+1+int(offset)+l:])
i += l + int(offset) + int(length)
}
i++
}
}
27 changes: 27 additions & 0 deletions ietf-28_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package quick

import (
"encoding/hex"
"strings"
"testing"
)

var (
packets = []string{
// from quic-tracker.info
`c2ff00001c088e20dc50ed17bacb08dd14022f16bccceb0044cad53606446555061869714b0d6794da7f136a21b293ad08781d99098f19525463b5ef9c62c9c8f96fa3814aa6118f37f6780512c72fd9c207b709edb879c4078df07483ed9db2e7dea22c9d1afd37358a0abf304e647420627f02cb17b38940ce261be5ba5f0996348342c096482e0066646004cba540a41adb5220fd54a7c62505d9c62866f58af275e56ca56d5492010ffe321a8f3dd19ec7269dc80884e5cf99c42387eee6ac220f4e2599b2598b56290391843f26ee690acea737f62b9bbab5f528284c409dc070997aaf52fa1d5d268ebe2e1a24b925af9a14001b937d41e175296d108707ce30555c17facf149baf83a5cd71306fe2e27a9fd363f22c8433ef9cf5624e8484ee37b728eec836a9d68d1f97a8168ad3816d606dba881dfbdef8423dca12a18f8d74a32180d52858ebe59440a65caa7e6c41d392148fece25b8780d9bc2dcbe6c331dbde0d1bf5181c5710c46b8ddaf5cd7445d5b063be4f1763d5eae6d16ea408bd3e18922bfc12630703ae5773820c40e9211359ce4a04061c24fea721a993b77244d8dbba77148ae70217a9172c299a8604e138a6420ab7cd60ba875f703832a932a69aa7c4a7a42c942726d7abb7928f807a96183acb74c8ef50979c67c9c24bd37187ac9d1b7c6e7a6a3ab2f2cfc4e9f7d2052dc06f5aed829cb2dfda76dac02641fc6383b82d32e7e187ca8cec4a2d1d99418d83e65b1965bc302b287fc8fff2e2bfd4bd0c3c08147623a7f84d29ffb02baa51c465375fee2a7ebff276e2c7d5359941b9a779fd8e1623e52e93d8e81e46e67b2cad4b350166a3768d3882b517b0e81e39efcf069f8271f9f855463d681af4ae823ea81cf5412c8db3f1f69f05d18423770a357ac6e062cc35a5f25a71ad184e842a5d5f4d05c5bd5be7040d253bd98e58cf36f30f7511e4b1a7e9ba3d36d8e0f7ef0972c2a62b4ddaa337ed8e288f68a5eab0d29ec167f80d7e7a877aa1be976f788d6695aad066413c24945c9f85664ae1ca1de576e18343804b2ed890deeb0533cd797e3260e6e75836f9e775cfdc3a578bce51f1490aab6681ed3673d6b7630267b03d3f5e37c199a78f9079bb6acb8fe100e68872ebf36abbfb7eb52d4f9bbfd9aca62c5d9cd7d0fc8f221a3f6b5685dcbe64f643d369f3ce8cd9c9aa5dd59b233caf98b4d20b88690c9cdc869098470a053fbb7bb5e289f69ed18b5972f0eb04ad3122e173786a4dd34d5921d57f7b07ffc7cb4164371dbd784c0c34a2b1fd503973d27b4df45cf7282acf402b2fc72c0965f7046c94ae7a022ac7720c43bde8af7db9238f98febbd5f1a670179f1714fccc89579a02f80b59a3a459a9fa91944ba00e17ef9f0050ce60b35a800eee80e3e125ec32eddb05b6e82ec907322f2b6c260495fe890a0c928e058b27af9f931e40a7686544224c094c273d35e07305b06783bb79e7646367b81b380a52e83f529cdb792687b10bf7f8f1bbdbb965f1fd2f903853103b614d61d15181b5f0fcb6b96b00bdbe9473662f2f0f86167d000814949b1b71bfd9144892db5c74e311c222775cb762442d8db4398eeaa0d2b40d8aaee29bb930ecd2e723d073fd8bb8e3c2af7ad0fb90f39df653cc4202d5688fc294e01ff37594d917ed2933f818d0a891632550dd8a05910d0b15f80ad96f1660ef4f3c7d803481fe1ceb93d56fac9422da792c55fb9177be674c0593125e9b84c24d027427c94c`,
`c4ff00001c08fe953fa4ecb7178e085b1624b027ea141b0044ca4f43cd9ed264f34274a199b1589e1dbc58100fbaf7ad413269029cd16d49336fb9fff7fea1e80c48de0106f751aac6a493fcfd66738a9d962a804b99b115e92b8812c0a95a74186d6594eb65ec0dc7b1055421358ab575aaf0eba1df75d5d31a607b50fc4944842ca060f74add8b2b7c932fe8eb0ee57d3f729fea3aa4e4384eb584d9df830924e547b16d6be1799bf01e0f36bab842afac44585012b1dd08c5dde93f94772b69c9c614c91ed53dc4c836a07bdcde143339c1ec91bf4374c90724ab810c458695cfb3d8f0f9b8be156db2b3648380bd2dd772889492c5366929a8cf85eaf4f0b639e8bcd1d256b10f701962232667816e34eb3ed990e576672a8aa53569c583b959b6bd5ca2671aaa47c79a9174c51c54c3982b0ae7e2b966750fe09ccc2a5a46580f1f42e437b80cf28f5487caa0c1239843ccdfaeff095361871a3e287650277dd859079917d0d45416a4409f494d9017fffff444f0b4868d2dc9f0f08ff30659e099b356cbdfd051cc2d906d4fa368d5c9a0453f0f8e4c58e6e0270fd882f97418c1dd00bf40773894c1513227064c32d0ceff82dc17d4f25ccc4d8fab0063ee04054381f075ca77704602ee9fa7d55281ed7ef36d7151779d1595cb8db2536a04e8aa0dea62ea9436b9af5d45b04605584253fc67be083333a63239787025b0b1e0311942c3e91bbb52e2a28f787ec589b7c4e5f5540cf0bad36b963a582fc739cd687120553d01d07f9a5422856941338acdc87de5d8ae1f76860f0ea497d875e6e938d2299e38ec8886872ba02ede3d2cabd0de47b61387f70ea81bee978dd0579d71f8a7c5e7b818e199fc1ebb5499854152fb18985a4b38e6e01a0319ddf2e282e65fdd3bbd6a0329fb2eded457e44deb052b0ba698ef962adcfd6dc886137a54671fbce1c80c17c30d9d28721cf3266dd2c12c144c35d580f3d3c93126f47894e837d770dcc19cc0eaa5e3335c16e95fbff5a5e2e8e3305924ed4c5bb5baaebfcac31cb37da7ac855d31a311d9226c13604c02f715bfca10a16ea904b33a8154f98e548bdd1720171bb0e28e7ca2cd303ec18d007b500eec7ce0891b14d4b39331d6d9e0688b88fa3e0e34b14a208283bca1616e3772ee6dcaf9e8bd6292dddf496bb43f48291546f5498ee13892ac93198559d9b08eef888d66550d0644c51e4bb05d3287f3db06cd88af1bcf23be6c01c1ea0a41f611ea129f1e2da364e98e30c100ec0fc4c6947602989849d3616df63be0913dae34fd5afdaa7eed96d46e1369f6569476876726aaaefa131c3b61056a160e17b044d921a16cc1d8f4bb120508346649017030ddf8b715fe69a4475e1936fb4b609534df99a3dbf84ab09129c0d012947b6db2092bd249d533ff2acd8c2d4d9c7a04819b8047ac0564c12cfa7188e7bcc9b866ae824d74480eedbcb013aed33af6fbd83f407e509b370482e359e824e3c25936d1e20a4bc8963f674e9c9b3f6d6483a5767ace1e2ef3d0a33bf4e7ea1f854b476f64e9dadf4bbe40ba7477b92654602fe0e98b4d308ce25ad41214b340d5af2c83154e28f1a267b061b2b87c811864b34f8f0bd76a4ac4496816974152cd447e06a56e79e0cf7f765b90218308202982ea7a5bdeed04828e02f03861a559cc689f486354c2c87fa446988c11c3f4c59e8f0fc0906749a1dd7deae3df6ba84121bc4de1acdd666a16f48d09f83d0ab42e5ec5c4d0cfc88f`,
}
)

func patch(s string) (r []byte) {
r, _ = hex.DecodeString(strings.ReplaceAll(strings.ReplaceAll(s, "\n", ""), " ", ""))
return
}
func TestDecodeIETF(t *testing.T) {
c := &CHLO{}
for _, s := range packets {
c.decodeIETF(patch(s))
t.Logf("%s\n", c)
}
}