From d1cf438564fe0ca6c98a21badf9d7a86f5cda5bc Mon Sep 17 00:00:00 2001 From: roz3x Date: Sat, 13 Jun 2020 13:27:38 +0000 Subject: [PATCH 1/2] added support for quic IETF-28 --- ietf-28.go | 186 ++++++++++++++++++++++++++++++++++++++++++++++++ ietf-28_test.go | 27 +++++++ 2 files changed, 213 insertions(+) create mode 100644 ietf-28.go create mode 100644 ietf-28_test.go diff --git a/ietf-28.go b/ietf-28.go new file mode 100644 index 0000000..836b4c1 --- /dev/null +++ b/ietf-28.go @@ -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++ + } +} diff --git a/ietf-28_test.go b/ietf-28_test.go new file mode 100644 index 0000000..9ea6cb8 --- /dev/null +++ b/ietf-28_test.go @@ -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) + } +} From 02a2ebb7024d46313e0c2db391316f85618ae74c Mon Sep 17 00:00:00 2001 From: roz3x Date: Sat, 13 Jun 2020 13:38:38 +0000 Subject: [PATCH 2/2] check for ietf version Signed-off-by: roz3x --- chlo.go | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/chlo.go b/chlo.go index 4ce524f..a374ea4 100644 --- a/chlo.go +++ b/chlo.go @@ -26,7 +26,7 @@ type CHLO struct { DataLength uint16 Tag string TagNumber uint16 - TagValues map[string]string + TagValues map[string]string TagsInOrder []string } @@ -49,6 +49,9 @@ 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"))) { @@ -56,13 +59,13 @@ func (ch *CHLO) DecodeCHLO(payload []byte) error { } // 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 } @@ -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 @@ -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 { @@ -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 } @@ -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]) }