diff --git a/README.md b/README.md index 9289fca..4445390 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,26 @@ you can set UConn.HandshakeStateBuilt = true, and marshal clientHello into UConn In this case you will be responsible for modifying other parts of Config and ClientHelloMsg to reflect your setup and not confuse "crypto/tls", which will be processing response from server. +### Fingerprinting Captured Client Hello +You can use a captured client hello to generate new ones that mimic/have the same properties as the original. +The generated client hellos _should_ look like they were generated from the same client software as the original fingerprinted bytes. +In order to do this: +1) Create a `ClientHelloSpec` from the raw bytes of the original client hello +2) Use `HelloCustom` as an argument for `UClient()` to get empty config +3) Use `ApplyPreset` with the generated `ClientHelloSpec` to set the appropriate connection properties +``` +uConn := UClient(&net.TCPConn{}, nil, HelloCustom) +fingerprinter := &Fingerprinter{} +generatedSpec, err := fingerprinter.FingerprintClientHello(rawCapturedClientHelloBytes) +if err != nil { + panic("fingerprinting failed: %v", err) +} +if err := uConn.ApplyPreset(generatedSpec); err != nil { + panic("applying generated spec failed: %v", err) +} +``` +The `rawCapturedClientHelloBytes` should be the full tls record, including the record type/version/length header. + ## Roller A simple wrapper, that allows to easily use multiple latest(auto-updated) fingerprints. diff --git a/testdata/Client-TLSv12-UTLS-ECDHE-RSA-AES128-GCM-SHA256-Chrome-58-fingerprinted b/testdata/Client-TLSv12-UTLS-ECDHE-RSA-AES128-GCM-SHA256-Chrome-58-fingerprinted new file mode 100644 index 0000000..9df3180 --- /dev/null +++ b/testdata/Client-TLSv12-UTLS-ECDHE-RSA-AES128-GCM-SHA256-Chrome-58-fingerprinted @@ -0,0 +1,91 @@ +>>> Flow 1 (client to server) +00000000 16 03 01 00 dd 01 00 00 d9 03 03 00 00 00 00 00 |................| +00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000020 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00 00 |........... ....| +00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 1c 0a 0a |................| +00000050 c0 2b c0 2f c0 2c c0 30 cc a9 cc a8 c0 13 c0 14 |.+./.,.0........| +00000060 00 9c 00 9d 00 2f 00 35 00 0a 01 00 00 74 0a 0a |...../.5.....t..| +00000070 00 00 ff 01 00 01 00 00 00 00 0b 00 09 00 00 06 |................| +00000080 66 6f 6f 62 61 72 00 17 00 00 00 23 00 00 00 0d |foobar.....#....| +00000090 00 14 00 12 04 03 08 04 04 01 05 03 08 05 05 01 |................| +000000a0 08 06 06 01 02 01 00 05 00 05 01 00 00 00 00 00 |................| +000000b0 12 00 00 00 10 00 0e 00 0c 02 68 32 08 68 74 74 |..........h2.htt| +000000c0 70 2f 31 2e 31 75 50 00 00 00 0b 00 02 01 00 00 |p/1.1uP.........| +000000d0 0a 00 0a 00 08 0a 0a 00 1d 00 17 00 18 1a 1a 00 |................| +000000e0 01 00 |..| +>>> Flow 2 (server to client) +00000000 16 03 03 00 5d 02 00 00 59 03 03 e3 95 26 49 88 |....]...Y....&I.| +00000010 52 b1 c0 bf a0 61 e1 d2 52 f3 cd 1f bb c9 92 66 |R....a..R......f| +00000020 1a 84 ae 97 61 fd 74 68 cc 02 37 20 55 af 37 bb |....a.th..7 U.7.| +00000030 58 09 95 db 22 49 cd b2 c7 ca 5c a5 be b5 b0 b1 |X..."I....\.....| +00000040 cb af ba 27 97 41 d9 30 02 22 da 5d c0 2f 00 00 |...'.A.0.".]./..| +00000050 11 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 17 |................| +00000060 00 00 16 03 03 02 59 0b 00 02 55 00 02 52 00 02 |......Y...U..R..| +00000070 4f 30 82 02 4b 30 82 01 b4 a0 03 02 01 02 02 09 |O0..K0..........| +00000080 00 e8 f0 9d 3f e2 5b ea a6 30 0d 06 09 2a 86 48 |....?.[..0...*.H| +00000090 86 f7 0d 01 01 0b 05 00 30 1f 31 0b 30 09 06 03 |........0.1.0...| +000000a0 55 04 0a 13 02 47 6f 31 10 30 0e 06 03 55 04 03 |U....Go1.0...U..| +000000b0 13 07 47 6f 20 52 6f 6f 74 30 1e 17 0d 31 36 30 |..Go Root0...160| +000000c0 31 30 31 30 30 30 30 30 30 5a 17 0d 32 35 30 31 |101000000Z..2501| +000000d0 30 31 30 30 30 30 30 30 5a 30 1a 31 0b 30 09 06 |01000000Z0.1.0..| +000000e0 03 55 04 0a 13 02 47 6f 31 0b 30 09 06 03 55 04 |.U....Go1.0...U.| +000000f0 03 13 02 47 6f 30 81 9f 30 0d 06 09 2a 86 48 86 |...Go0..0...*.H.| +00000100 f7 0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 |...........0....| +00000110 81 00 db 46 7d 93 2e 12 27 06 48 bc 06 28 21 ab |...F}...'.H..(!.| +00000120 7e c4 b6 a2 5d fe 1e 52 45 88 7a 36 47 a5 08 0d |~...]..RE.z6G...| +00000130 92 42 5b c2 81 c0 be 97 79 98 40 fb 4f 6d 14 fd |.B[.....y.@.Om..| +00000140 2b 13 8b c2 a5 2e 67 d8 d4 09 9e d6 22 38 b7 4a |+.....g....."8.J| +00000150 0b 74 73 2b c2 34 f1 d1 93 e5 96 d9 74 7b f3 58 |.ts+.4......t{.X| +00000160 9f 6c 61 3c c0 b0 41 d4 d9 2b 2b 24 23 77 5b 1c |.la<..A..++$#w[.| +00000170 3b bd 75 5d ce 20 54 cf a1 63 87 1d 1e 24 c4 f3 |;.u]. T..c...$..| +00000180 1d 1a 50 8b aa b6 14 43 ed 97 a7 75 62 f4 14 c8 |..P....C...ub...| +00000190 52 d7 02 03 01 00 01 a3 81 93 30 81 90 30 0e 06 |R.........0..0..| +000001a0 03 55 1d 0f 01 01 ff 04 04 03 02 05 a0 30 1d 06 |.U...........0..| +000001b0 03 55 1d 25 04 16 30 14 06 08 2b 06 01 05 05 07 |.U.%..0...+.....| +000001c0 03 01 06 08 2b 06 01 05 05 07 03 02 30 0c 06 03 |....+.......0...| +000001d0 55 1d 13 01 01 ff 04 02 30 00 30 19 06 03 55 1d |U.......0.0...U.| +000001e0 0e 04 12 04 10 9f 91 16 1f 43 43 3e 49 a6 de 6d |.........CC>I..m| +000001f0 b6 80 d7 9f 60 30 1b 06 03 55 1d 23 04 14 30 12 |....`0...U.#..0.| +00000200 80 10 48 13 49 4d 13 7e 16 31 bb a3 01 d5 ac ab |..H.IM.~.1......| +00000210 6e 7b 30 19 06 03 55 1d 11 04 12 30 10 82 0e 65 |n{0...U....0...e| +00000220 78 61 6d 70 6c 65 2e 67 6f 6c 61 6e 67 30 0d 06 |xample.golang0..| +00000230 09 2a 86 48 86 f7 0d 01 01 0b 05 00 03 81 81 00 |.*.H............| +00000240 9d 30 cc 40 2b 5b 50 a0 61 cb ba e5 53 58 e1 ed |.0.@+[P.a...SX..| +00000250 83 28 a9 58 1a a9 38 a4 95 a1 ac 31 5a 1a 84 66 |.(.X..8....1Z..f| +00000260 3d 43 d3 2d d9 0b f2 97 df d3 20 64 38 92 24 3a |=C.-...... d8.$:| +00000270 00 bc cf 9c 7d b7 40 20 01 5f aa d3 16 61 09 a2 |....}.@ ._...a..| +00000280 76 fd 13 c3 cc e1 0c 5c ee b1 87 82 f1 6c 04 ed |v......\.....l..| +00000290 73 bb b3 43 77 8d 0c 1c f1 0f a1 d8 40 83 61 c9 |s..Cw.......@.a.| +000002a0 4c 72 2b 9d ae db 46 06 06 4d f4 c1 b3 3e c0 d1 |Lr+...F..M...>..| +000002b0 bd 42 d4 db fe 3d 13 60 84 5c 21 d3 3b e9 fa e7 |.B...=.`.\!.;...| +000002c0 16 03 03 00 ac 0c 00 00 a8 03 00 1d 20 c0 48 97 |............ .H.| +000002d0 bf 0d 8d 9e f9 bb 77 f4 0f 86 cc 81 ad 94 74 0e |......w.......t.| +000002e0 9d ef b2 27 52 f2 cf 60 1c 84 4b c3 59 08 04 00 |...'R..`..K.Y...| +000002f0 80 02 45 0b 36 e5 4b 48 d8 c3 7e 4f c3 43 6b b4 |..E.6.KH..~O.Ck.| +00000300 bd 6a a2 04 ee c4 59 32 a3 d2 5d 72 8d d8 49 b9 |.j....Y2..]r..I.| +00000310 de 31 04 bf dc f3 1a d8 31 d4 eb b4 98 e4 2c d3 |.1......1.....,.| +00000320 50 42 f6 cc 41 2f 75 aa 63 19 99 49 38 ec 0b ed |PB..A/u.c..I8...| +00000330 59 6a 82 58 ea 9c 58 d8 6b 88 60 35 b7 06 21 10 |Yj.X..X.k.`5..!.| +00000340 54 c8 d4 6b af fc 6e 2b 90 15 b1 87 ed aa 96 bb |T..k..n+........| +00000350 ad 8d dc e1 7b 48 bf da d2 70 eb cf 73 4a e6 60 |....{H...p..sJ.`| +00000360 ac ef 39 57 a6 ff 5d 34 bb 7b e1 e0 e5 11 67 62 |..9W..]4.{....gb| +00000370 6c 16 03 03 00 04 0e 00 00 00 |l.........| +>>> Flow 3 (client to server) +00000000 16 03 03 00 25 10 00 00 21 20 2f e5 7d a3 47 cd |....%...! /.}.G.| +00000010 62 43 15 28 da ac 5f bb 29 07 30 ff f6 84 af c4 |bC.(.._.).0.....| +00000020 cf c2 ed 90 99 5f 58 cb 3b 74 14 03 03 00 01 01 |....._X.;t......| +00000030 16 03 03 00 28 00 00 00 00 00 00 00 00 eb f8 36 |....(..........6| +00000040 16 8e 9a 9f 52 62 a6 85 c3 05 4b 76 16 35 63 fe |....Rb....Kv.5c.| +00000050 0c c3 4a 1e f0 0e 24 b5 6e db 0d bc 65 |..J...$.n...e| +>>> Flow 4 (server to client) +00000000 14 03 03 00 01 01 16 03 03 00 28 5f 6d 17 67 11 |..........(_m.g.| +00000010 3a 9b 08 87 88 c2 6d ee 8a 1a 0f e9 30 fe 9a 30 |:.....m.....0..0| +00000020 63 ad 2e a1 a6 8f b8 4e fa c9 65 64 87 96 b4 66 |c......N..ed...f| +00000030 39 03 7b |9.{| +>>> Flow 5 (client to server) +00000000 17 03 03 00 1e 00 00 00 00 00 00 00 01 68 fb 78 |.............h.x| +00000010 2f 1b 5b 21 fc e3 f5 39 69 29 63 35 54 71 06 4a |/.[!...9i)c5Tq.J| +00000020 fc f1 f9 15 03 03 00 1a 00 00 00 00 00 00 00 02 |................| +00000030 10 fb 34 b1 37 06 12 4b c2 17 dd ef 2f 52 2e 22 |..4.7..K..../R."| +00000040 60 4f |`O| diff --git a/testdata/Client-TLSv13-UTLS-TLS_AES_128_GCM_SHA256-Chrome-70-fingerprinted b/testdata/Client-TLSv13-UTLS-TLS_AES_128_GCM_SHA256-Chrome-70-fingerprinted new file mode 100644 index 0000000..8eac1fc --- /dev/null +++ b/testdata/Client-TLSv13-UTLS-TLS_AES_128_GCM_SHA256-Chrome-70-fingerprinted @@ -0,0 +1,107 @@ +>>> Flow 1 (client to server) +00000000 16 03 01 02 00 01 00 01 fc 03 03 00 00 00 00 00 |................| +00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000020 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00 00 |........... ....| +00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 22 0a 0a |............."..| +00000050 13 01 13 02 13 03 c0 2b c0 2f c0 2c c0 30 cc a9 |.......+./.,.0..| +00000060 cc a8 c0 13 c0 14 00 9c 00 9d 00 2f 00 35 00 0a |.........../.5..| +00000070 01 00 01 91 0a 0a 00 00 ff 01 00 01 00 00 00 00 |................| +00000080 0b 00 09 00 00 06 66 6f 6f 62 61 72 00 17 00 00 |......foobar....| +00000090 00 23 00 00 00 0d 00 14 00 12 04 03 08 04 04 01 |.#..............| +000000a0 05 03 08 05 05 01 08 06 06 01 02 01 00 05 00 05 |................| +000000b0 01 00 00 00 00 00 12 00 00 00 10 00 0e 00 0c 02 |................| +000000c0 68 32 08 68 74 74 70 2f 31 2e 31 75 50 00 00 00 |h2.http/1.1uP...| +000000d0 0b 00 02 01 00 00 33 00 2b 00 29 0a 0a 00 01 00 |......3.+.).....| +000000e0 00 1d 00 20 2f e5 7d a3 47 cd 62 43 15 28 da ac |... /.}.G.bC.(..| +000000f0 5f bb 29 07 30 ff f6 84 af c4 cf c2 ed 90 99 5f |_.).0.........._| +00000100 58 cb 3b 74 00 2d 00 02 01 01 00 2b 00 0b 0a 0a |X.;t.-.....+....| +00000110 0a 03 04 03 03 03 02 03 01 00 0a 00 0a 00 08 0a |................| +00000120 0a 00 1d 00 17 00 18 00 1b 00 03 02 00 02 1a 1a |................| +00000130 00 01 00 00 15 00 ce 00 00 00 00 00 00 00 00 00 |................| +00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000200 00 00 00 00 00 |.....| +>>> Flow 2 (server to client) +00000000 16 03 03 00 7a 02 00 00 76 03 03 c5 4a de 6c e8 |....z...v...J.l.| +00000010 25 6d db 77 3b ef e6 22 28 0a 19 ec 58 2d ae a1 |%m.w;.."(...X-..| +00000020 57 f6 1b 08 52 c8 5d 79 4b b1 a4 20 00 00 00 00 |W...R.]yK.. ....| +00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000040 00 00 00 00 00 00 00 00 00 00 00 00 13 01 00 00 |................| +00000050 2e 00 2b 00 02 03 04 00 33 00 24 00 1d 00 20 df |..+.....3.$... .| +00000060 ec d9 e6 68 f6 90 5d 47 35 4b 5c 4d 2f 91 fe 68 |...h..]G5K\M/..h| +00000070 43 d8 0f f5 1e de 21 03 8e 4f d4 b1 f7 8f 58 14 |C.....!..O....X.| +00000080 03 03 00 01 01 17 03 03 00 17 10 ec 0b 62 a4 0e |.............b..| +00000090 bb 5d ea 09 4a 83 92 13 c8 a7 5e d6 8e 1b 11 91 |.]..J.....^.....| +000000a0 6b 17 03 03 02 6d ff 05 19 3f ec 22 b2 6d 0c ea |k....m...?.".m..| +000000b0 4d 4f 32 26 11 c6 b3 fd 29 04 e5 dc 50 ec ff 1f |MO2&....)...P...| +000000c0 fb af 2e c9 a0 6c 4a 58 17 b1 04 e8 3c bc 62 cd |.....lJX....<.b.| +000000d0 08 c1 42 d1 be 56 5d 70 25 f2 f1 37 be df 9f a7 |..B..V]p%..7....| +000000e0 1b f8 2b f9 3f 1c cc ed 66 d4 06 19 0e f9 ef bc |..+.?...f.......| +000000f0 aa ac c3 89 7e 22 30 5f 8e b7 ca 54 93 ae 29 b3 |....~"0_...T..).| +00000100 5d 9d 9e c2 9c 03 3f 0e 25 1f 9a 73 13 74 18 03 |].....?.%..s.t..| +00000110 94 7d 23 54 5b bd 9c 82 d1 ae cd 42 95 9b 37 a7 |.}#T[......B..7.| +00000120 7f 1c 75 5c 3a 21 5d aa 34 bb 9a d1 26 75 09 a7 |..u\:!].4...&u..| +00000130 4f 63 24 c2 50 90 28 a9 b3 92 61 ac 9f 68 f6 50 |Oc$.P.(...a..h.P| +00000140 eb 16 3f 1a 96 3f 36 2f e8 16 d2 2c 66 ee 80 b7 |..?..?6/...,f...| +00000150 0c 58 bf 0d f4 38 09 cb ad fa aa 22 a0 e7 66 bb |.X...8....."..f.| +00000160 27 7f 57 5c 82 24 f6 b5 af 4f a7 90 ba 60 62 3c |'.W\.$...O...`b<| +00000170 4d 74 b4 33 aa ab 23 07 11 84 3e bd 98 aa d5 42 |Mt.3..#...>....B| +00000180 a8 43 79 58 31 e7 e2 e6 52 bd 30 60 27 f9 da 17 |.CyX1...R.0`'...| +00000190 16 0b 7c 15 14 30 0e ae 11 16 e1 71 c9 cc f8 2f |..|..0.....q.../| +000001a0 91 31 34 09 95 6a 10 4b 04 73 27 77 73 4f 2e 7a |.14..j.K.s'wsO.z| +000001b0 27 b4 79 5e 3a 50 42 60 f8 e0 48 98 a3 b0 5c 6d |'.y^:PB`..H...\m| +000001c0 36 bd 2d 42 3c 82 0b 7e 81 55 a1 74 75 83 80 22 |6.-B<..~.U.tu.."| +000001d0 28 e0 41 4e 7d f5 7f f2 2a 5b b0 7d 39 ab 45 80 |(.AN}...*[.}9.E.| +000001e0 17 4c 41 b5 ec e5 4f 45 e0 ee 6c c0 64 0a 9a c3 |.LA...OE..l.d...| +000001f0 b4 46 df d6 bf 2b 23 96 44 e5 fc 29 9c d3 f0 7f |.F...+#.D..)....| +00000200 dd f9 63 86 07 90 be 0a d4 19 2c 1d 52 db aa 3d |..c.......,.R..=| +00000210 92 0d 07 94 71 f9 78 e6 41 84 4f 94 30 1c b5 ae |....q.x.A.O.0...| +00000220 3f de 5e 95 13 23 4b 43 42 87 68 05 48 41 d6 52 |?.^..#KCB.h.HA.R| +00000230 bc af 11 22 25 4a a3 2a 1c 7e 49 a2 ed 0f c5 27 |..."%J.*.~I....'| +00000240 3c 39 48 5b af a9 49 81 f2 b2 73 e3 dc de d5 fd |<9H[..I...s.....| +00000250 1c 32 a3 c8 7d bb 8c 5e 4c 25 24 a6 ae 86 d0 25 |.2..}..^L%$....%| +00000260 83 cb 38 16 62 27 f3 d8 57 cf eb 2f 27 24 55 c5 |..8.b'..W../'$U.| +00000270 7b 65 7a 25 b4 40 97 18 79 e1 fb 3b b6 12 2e 10 |{ez%.@..y..;....| +00000280 ac 89 21 9d f7 5a 3e 00 65 f8 6f 31 4b 02 2d 9f |..!..Z>.e.o1K.-.| +00000290 07 00 1e 2d 85 27 c1 17 4a 26 78 4a 0e eb ca b6 |...-.'..J&xJ....| +000002a0 32 57 48 7f 87 5a b2 ce 24 03 75 b9 d3 94 48 f9 |2WH..Z..$.u...H.| +000002b0 f4 b1 e8 89 ab 7e c9 75 c6 0d 18 cd c8 3a 05 9f |.....~.u.....:..| +000002c0 8a d9 42 39 30 69 0a d7 56 9d 3b 51 af 13 6a 59 |..B90i..V.;Q..jY| +000002d0 ea 72 e2 75 8a aa 3f 25 49 93 e5 a2 da f6 31 67 |.r.u..?%I.....1g| +000002e0 6a 68 af b4 6e 14 8e 75 91 b5 3d 24 52 11 da 0b |jh..n..u..=$R...| +000002f0 d0 66 db 8b 61 13 c7 93 0d bd bc b9 78 85 fd 12 |.f..a.......x...| +00000300 b5 56 84 3d 7b 75 9f 50 bf d6 a4 eb 8d 24 4c 11 |.V.={u.P.....$L.| +00000310 1c 16 4d 17 03 03 00 99 a8 de 68 0a 8c 77 18 87 |..M.......h..w..| +00000320 24 be eb 40 1d a0 f6 92 f2 ac 90 15 b7 6c 0b cc |$..@.........l..| +00000330 6c 85 54 42 d5 89 91 57 bc dd b6 7f ee a2 e0 6f |l.TB...W.......o| +00000340 17 92 23 cc 73 1e 16 f3 49 8c 82 ef 15 4f 8a be |..#.s...I....O..| +00000350 bd 07 8f 72 5c b1 fc b3 8c e9 a3 e1 ec 9d 7a a2 |...r\.........z.| +00000360 8f 68 1a 91 ab e8 d7 3a 67 b5 bc 8a 72 1f 2d 51 |.h.....:g...r.-Q| +00000370 ba 1a 2b 75 24 3f 0c 8a 79 a9 ac ba 71 da 0c 60 |..+u$?..y...q..`| +00000380 55 3b 60 2c 51 18 f8 ab c2 45 33 74 76 2e 2c eb |U;`,Q....E3tv.,.| +00000390 d0 d5 36 10 5a ee 88 2a 96 15 19 f3 aa ba b9 71 |..6.Z..*.......q| +000003a0 e6 40 42 a0 92 3a 76 22 de a6 f9 49 22 35 c2 56 |.@B..:v"...I"5.V| +000003b0 b2 17 03 03 00 35 5d 29 4e e1 67 87 54 08 21 98 |.....5])N.g.T.!.| +000003c0 a1 dd 93 e9 22 d2 4f 65 5e 41 99 1d 7e 72 c0 20 |....".Oe^A..~r. | +000003d0 2b f9 67 7a 79 68 3a 00 b1 99 39 fe 33 27 5e 4b |+.gzyh:...9.3'^K| +000003e0 67 aa fd 0e 3c b6 1f fd cb 91 e1 |g...<......| +>>> Flow 3 (client to server) +00000000 14 03 03 00 01 01 17 03 03 00 35 2c 4a 03 fc 16 |..........5,J...| +00000010 01 d8 10 25 27 1e 2d 0e b8 62 9c b4 f3 bd 50 a2 |...%'.-..b....P.| +00000020 d1 e7 9e 5f cb 2c d5 ce 5a 56 c5 50 ab 12 4b 69 |..._.,..ZV.P..Ki| +00000030 4c e3 eb 20 ca 91 e0 07 9c b4 6c 5b 79 d3 c5 a4 |L.. ......l[y...| +00000040 17 03 03 00 17 12 d5 76 2d 12 23 20 e5 a4 35 45 |.......v-.# ..5E| +00000050 7d 5a e1 bc a3 da 08 11 5e a5 3b 78 17 03 03 00 |}Z......^.;x....| +00000060 13 d7 07 1e 17 ef df 5d da 27 29 52 5a 9a ed a5 |.......].')RZ...| +00000070 07 1b 81 f1 |....| diff --git a/testdata/Client-TLSv13-UTLS-TLS_AES_128_GCM_SHA256-raw-capture-fingerprinted b/testdata/Client-TLSv13-UTLS-TLS_AES_128_GCM_SHA256-raw-capture-fingerprinted new file mode 100644 index 0000000..fe85594 --- /dev/null +++ b/testdata/Client-TLSv13-UTLS-TLS_AES_128_GCM_SHA256-raw-capture-fingerprinted @@ -0,0 +1,107 @@ +>>> Flow 1 (client to server) +00000000 16 03 01 02 00 01 00 01 fc 03 03 00 00 00 00 00 |................| +00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000020 00 00 00 00 00 00 00 00 00 00 00 20 00 00 00 00 |........... ....| +00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 24 13 01 |.............$..| +00000050 13 03 13 02 c0 2b c0 2f cc a9 cc a8 c0 2c c0 30 |.....+./.....,.0| +00000060 c0 0a c0 09 c0 13 c0 14 00 9c 00 9d 00 2f 00 35 |............./.5| +00000070 00 0a 01 00 01 8f 00 00 00 05 00 03 00 00 00 00 |................| +00000080 17 00 00 ff 01 00 01 00 00 0a 00 0e 00 0c 00 1d |................| +00000090 00 17 00 18 00 19 01 00 01 01 00 0b 00 02 01 00 |................| +000000a0 00 10 00 0e 00 0c 02 68 32 08 68 74 74 70 2f 31 |.......h2.http/1| +000000b0 2e 31 00 05 00 05 01 00 00 00 00 00 33 00 6b 00 |.1..........3.k.| +000000c0 69 00 1d 00 20 2f e5 7d a3 47 cd 62 43 15 28 da |i... /.}.G.bC.(.| +000000d0 ac 5f bb 29 07 30 ff f6 84 af c4 cf c2 ed 90 99 |._.).0..........| +000000e0 5f 58 cb 3b 74 00 17 00 41 04 1e 18 37 ef 0d 19 |_X.;t...A...7...| +000000f0 51 88 35 75 71 b5 e5 54 5b 12 2e 8f 09 67 fd a7 |Q.5uq..T[....g..| +00000100 24 20 3e b2 56 1c ce 97 28 5e f8 2b 2d 4f 9e f1 |$ >.V...(^.+-O..| +00000110 07 9f 6c 4b 5b 83 56 e2 32 42 e9 58 b6 d7 49 a6 |..lK[.V.2B.X..I.| +00000120 b5 68 1a 41 03 56 6b dc 5a 89 00 2b 00 05 04 03 |.h.A.Vk.Z..+....| +00000130 04 03 03 00 0d 00 18 00 16 04 03 05 03 06 03 08 |................| +00000140 04 08 05 08 06 04 01 05 01 06 01 02 03 02 01 00 |................| +00000150 1c 00 02 40 01 00 15 00 ac 00 00 00 00 00 00 00 |...@............| +00000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000200 00 00 00 00 00 |.....| +>>> Flow 2 (server to client) +00000000 16 03 03 00 7a 02 00 00 76 03 03 0c f4 d3 9a 51 |....z...v......Q| +00000010 ee f9 a7 5d ca 63 bc d1 41 e4 18 b3 11 4e b7 27 |...].c..A....N.'| +00000020 b2 81 9d ff 98 da 3c 9a 5c 0b 07 20 00 00 00 00 |......<.\.. ....| +00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000040 00 00 00 00 00 00 00 00 00 00 00 00 13 01 00 00 |................| +00000050 2e 00 2b 00 02 03 04 00 33 00 24 00 1d 00 20 3d |..+.....3.$... =| +00000060 58 17 cf 6c 8d 8a 8f f3 79 a9 c4 c9 3f 5e 15 f8 |X..l....y...?^..| +00000070 49 6a e0 61 be 0d 62 fd 20 6a ad 9d 66 71 57 14 |Ij.a..b. j..fqW.| +00000080 03 03 00 01 01 17 03 03 00 17 26 47 66 c9 a5 1d |..........&Gf...| +00000090 0c 10 9f 98 a7 47 33 8b ef aa 90 41 13 1c 34 b3 |.....G3....A..4.| +000000a0 ed 17 03 03 02 6d 8e 5f cb 3d 6f 38 a4 96 a4 7f |.....m._.=o8....| +000000b0 64 f4 7b 90 a8 83 f7 a9 42 ff 86 dd 18 5e aa 5f |d.{.....B....^._| +000000c0 01 0d 73 8f 4c 9e d2 f9 e2 c1 83 ec c5 cf 7a 6a |..s.L.........zj| +000000d0 20 2e 1f 81 55 0b cb e4 2e f9 5a cf 2d 13 5b 8c | ...U.....Z.-.[.| +000000e0 4b 15 3f ac 5c 35 87 60 a7 53 33 93 d9 8c b6 19 |K.?.\5.`.S3.....| +000000f0 cf 4f cb 18 0d 53 e1 11 3d f7 c0 f0 65 1c 1f 7a |.O...S..=...e..z| +00000100 92 fd 9f bb 93 08 b5 68 29 3a 5c b0 24 7a e9 37 |.......h):\.$z.7| +00000110 a8 e0 e8 a1 fd c2 34 d4 2f ae 48 dc 4d 78 95 dd |......4./.H.Mx..| +00000120 ae fe 88 02 74 bc d9 03 13 11 49 d1 ba b1 fb 49 |....t.....I....I| +00000130 92 8e ae ef 43 3e 19 85 70 46 28 20 b4 4b 6b 4e |....C>..pF( .KkN| +00000140 b1 8f a8 4b ce 48 4c 6f f0 12 5c 10 de 9e 37 d7 |...K.HLo..\...7.| +00000150 82 f3 f7 5e aa 3f 25 0b 74 e7 fa 7a 26 7b 88 c1 |...^.?%.t..z&{..| +00000160 a9 df cb 70 98 91 4a 4a ec f3 62 ef 13 42 71 5a |...p..JJ..b..BqZ| +00000170 ca ca 59 c6 4b c0 8b 0b 82 51 14 11 8c 7a c0 d6 |..Y.K....Q...z..| +00000180 42 5a 1d 7c ff d7 5e de 6e 99 f8 ee 55 aa 8f 27 |BZ.|..^.n...U..'| +00000190 b1 c0 65 a8 52 74 42 35 0d e5 3e 0e 0e 0c c4 a3 |..e.RtB5..>.....| +000001a0 32 63 1d 72 57 55 f1 a5 d5 f9 95 99 31 1c 09 d6 |2c.rWU......1...| +000001b0 48 58 cf 6e f7 8d a9 a7 c6 74 91 77 6c 28 13 65 |HX.n.....t.wl(.e| +000001c0 b8 53 f8 4b e1 1b 32 39 31 36 41 d2 35 4c 7b 82 |.S.K..2916A.5L{.| +000001d0 75 bc a6 66 b3 d2 df 51 c8 da 65 7e 94 93 0d e8 |u..f...Q..e~....| +000001e0 33 4c f8 c6 16 50 12 c7 06 0c 9e f9 57 36 3c e3 |3L...P......W6<.| +000001f0 94 ba 50 c7 ab 27 33 cb 7b 82 ce fc 39 1a c8 7f |..P..'3.{...9...| +00000200 59 85 d7 3c 37 ff fc b8 03 2b da 62 80 d3 c0 c6 |Y..<7....+.b....| +00000210 96 d0 fc 50 08 7e 02 25 c7 ca b6 6a 27 cc b8 d2 |...P.~.%...j'...| +00000220 58 15 18 58 01 67 10 c8 a0 0b b1 39 34 ce fe 64 |X..X.g.....94..d| +00000230 25 22 68 39 82 a7 21 ab e2 41 c6 35 dd 40 5c e7 |%"h9..!..A.5.@\.| +00000240 88 e5 b0 9e 8d 20 2a 9a f2 f7 6e d0 6e fe 06 9f |..... *...n.n...| +00000250 9f b4 56 e8 e4 1d cd 55 5c 68 6e 8d 40 77 e6 d5 |..V....U\hn.@w..| +00000260 98 73 8f d2 53 34 1c 15 aa ad 27 51 ef 31 b2 0f |.s..S4....'Q.1..| +00000270 94 d5 69 1f 70 ec e3 ac 0c cc a3 a1 d7 61 ee 1e |..i.p........a..| +00000280 15 83 bd 41 9f 4c 2b d3 cc d8 06 d8 d8 a4 d3 64 |...A.L+........d| +00000290 95 49 e4 03 73 54 f6 f7 24 3d c3 a6 f3 6a 19 dc |.I..sT..$=...j..| +000002a0 a3 00 00 7e 56 f2 f9 26 5f 09 ca c5 b0 eb 58 d8 |...~V..&_.....X.| +000002b0 22 93 c9 ce 5d f7 63 72 7d 72 56 77 38 08 f9 86 |"...].cr}rVw8...| +000002c0 8e 8f ad 8c 02 64 36 fe b8 09 d7 0e 58 73 aa 99 |.....d6.....Xs..| +000002d0 d1 d1 60 4c 65 5f 80 ed 13 0d a0 19 b4 b5 d3 8a |..`Le_..........| +000002e0 ba dc 84 94 af 7a 77 61 b0 bb 01 38 d2 6d 82 6d |.....zwa...8.m.m| +000002f0 22 08 17 17 16 0c 48 25 c3 b9 35 ff 78 7b ab af |".....H%..5.x{..| +00000300 63 9a d9 83 d1 65 2a 3e 7f b0 90 dd 67 c1 be 0a |c....e*>....g...| +00000310 14 e5 17 17 03 03 00 99 ff 0b c2 4f 7d 1f 7a 8e |...........O}.z.| +00000320 15 a1 53 35 e1 bd c7 20 ad 87 e9 22 99 0b 46 76 |..S5... ..."..Fv| +00000330 1f 9d 87 6c 64 ef 5f 34 66 fa c5 6a af bd c5 34 |...ld._4f..j...4| +00000340 c8 32 f4 c4 06 3e 85 b4 41 e9 1a e8 98 c1 69 20 |.2...>..A.....i | +00000350 3e ec 84 ae 63 27 98 e5 81 07 89 8b d5 74 6f b1 |>...c'.......to.| +00000360 80 b9 fb e0 f8 d8 ef 3e 00 d8 57 97 41 87 26 02 |.......>..W.A.&.| +00000370 a9 58 83 5a 5f ad 3c 1f ed 24 50 fe a3 3b 70 be |.X.Z_.<..$P..;p.| +00000380 bd 03 19 d6 6a 46 72 7a 68 36 bb 40 e5 88 bc 1e |....jFrzh6.@....| +00000390 cc 02 3d 09 0b 59 be 40 71 44 0c 34 17 40 bf 46 |..=..Y.@qD.4.@.F| +000003a0 c6 6c 01 05 bd a1 83 e7 ad 12 1c 9d 78 c5 08 a2 |.l..........x...| +000003b0 26 17 03 03 00 35 0b c5 51 ec 7b c5 96 9d 93 b6 |&....5..Q.{.....| +000003c0 1e 71 3f 2d 34 32 50 c1 77 08 ce e9 83 59 47 da |.q?-42P.w....YG.| +000003d0 b5 8c 5e cf 58 89 da 98 c4 b8 73 c0 1c f6 30 bd |..^.X.....s...0.| +000003e0 c2 b1 34 7a 1a 54 ad ef c2 2b 2b |..4z.T...++| +>>> Flow 3 (client to server) +00000000 14 03 03 00 01 01 17 03 03 00 35 5e 98 29 74 04 |..........5^.)t.| +00000010 bb f5 5e 17 dc 36 8f cb 3c e0 45 1a d2 54 ae f6 |..^..6..<.E..T..| +00000020 3a 36 59 c0 fa dc 2d e7 73 66 ca af d5 c7 e6 0d |:6Y...-.sf......| +00000030 e1 23 17 a3 88 85 16 83 16 ec 78 d3 78 bc 39 ba |.#........x.x.9.| +00000040 17 03 03 00 17 91 53 7f ab 3a 4a a0 d2 e5 0a 7e |......S..:J....~| +00000050 e9 52 2a fa de f8 f8 20 76 ad 9f 48 17 03 03 00 |.R*.... v..H....| +00000060 13 0d 74 fc f8 27 c1 fd 06 b1 3a 5c 2d 37 63 dd |..t..'....:\-7c.| +00000070 5c 59 28 9a |\Y(.| diff --git a/u_common.go b/u_common.go index 2beb1e6..9c6e238 100644 --- a/u_common.go +++ b/u_common.go @@ -162,6 +162,20 @@ var ( // https://tools.ietf.org/html/draft-ietf-tls-grease-01 const GREASE_PLACEHOLDER = 0x0a0a +func isGREASEUint16(v uint16) bool { + // First byte is same as second byte + // and lowest nibble is 0xa + return ((v >> 8) == v&0xff) && v&0xf == 0xa +} + +func unGREASEUint16(v uint16) uint16 { + if isGREASEUint16(v) { + return GREASE_PLACEHOLDER + } else { + return v + } +} + // utlsMacSHA384 returns a SHA-384 based MAC. These are only supported in TLS 1.2 // so the given version is ignored. func utlsMacSHA384(version uint16, key []byte) macFunction { diff --git a/u_common_test.go b/u_common_test.go new file mode 100644 index 0000000..c6667fe --- /dev/null +++ b/u_common_test.go @@ -0,0 +1,33 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tls + +import ( + "testing" +) + +func TestUTLSIsGrease(t *testing.T) { + + var testMap = []struct { + version uint16 + isGREASE bool + }{ + {0x0a0a, true}, + {0x1a1a, true}, + {0x2a1a, false}, + {0x2a2a, true}, + {0x1234, false}, + {0x1a2a, false}, + {0xdeed, false}, + {0xb1b1, false}, + {0x0b0b, false}, + } + + for _, testCase := range testMap { + if isGREASEUint16(testCase.version) != testCase.isGREASE { + t.Errorf("misidentified GREASE: testing %x, isGREASE: %v", testCase.version, isGREASEUint16(testCase.version)) + } + } +} diff --git a/u_conn_test.go b/u_conn_test.go index c12a244..daec15e 100644 --- a/u_conn_test.go +++ b/u_conn_test.go @@ -17,6 +17,28 @@ import ( "time" ) +// helloStrategy is a sum type interface which allows us to pass either a ClientHelloID or a ClientHelloSpec and then act accordingly +type helloStrategy interface { + helloName() string +} + +type helloID struct { + id ClientHelloID +} + +func (hid *helloID) helloName() string { + return hid.id.Str() +} + +type helloSpec struct { + name string + spec *ClientHelloSpec +} + +func (hs *helloSpec) helloName() string { + return hs.name +} + func TestUTLSMarshalNoOp(t *testing.T) { str := "We rely on clientHelloMsg.marshal() not doing anything if clientHelloMsg.raw is set" uconn := UClient(&net.TCPConn{}, &Config{ServerName: "foobar"}, HelloGolang) @@ -32,146 +54,146 @@ func TestUTLSMarshalNoOp(t *testing.T) { } func TestUTLSHandshakeClientParrotGolang(t *testing.T) { - helloID := HelloGolang + hello := &helloID{HelloGolang} - testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, helloID) - testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, helloID) + testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello) + testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello) - testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, helloID) - testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, helloID) - testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, helloID) - testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t, helloID) + testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello) + testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello) + testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello) + testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t, hello) - testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t, helloID) + testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t, hello) } func TestUTLSHandshakeClientParrotChrome_70(t *testing.T) { - helloID := HelloChrome_70 + hello := &helloID{HelloChrome_70} - testUTLSHandshakeClientTLS13_AES_128_GCM_SHA256(t, helloID) - testUTLSHandshakeClientTLS13_AES_256_GCM_SHA384(t, helloID) - testUTLSHandshakeClientTLS13_CHACHA20_POLY1305_SHA256(t, helloID) - //testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, helloID) - //testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, helloID) + testUTLSHandshakeClientTLS13_AES_128_GCM_SHA256(t, hello) + testUTLSHandshakeClientTLS13_AES_256_GCM_SHA384(t, hello) + testUTLSHandshakeClientTLS13_CHACHA20_POLY1305_SHA256(t, hello) + //testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello) + //testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, hello) - //testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, helloID) - testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, helloID) + //testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello) + testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello) - testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, helloID) + testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello) - testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t, helloID) + testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t, hello) } func TestUTLSHandshakeClientParrotChrome_58(t *testing.T) { - helloID := HelloChrome_58 + hello := &helloID{HelloChrome_58} // TODO: EC tests below are disabled because latest version of reference OpenSSL doesn't support p256 nor p384 // nor X25519 and I can't find configuration flag to enable it. Therefore I can't record replays. - //testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, helloID) - //testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, helloID) + //testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello) + //testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, hello) - //testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, helloID) - testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, helloID) + //testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello) + testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello) - testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, helloID) + testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello) - testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t, helloID) + testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t, hello) } func TestUTLSHandshakeClientParrotFirefox_63(t *testing.T) { - helloID := HelloFirefox_63 + hello := &helloID{HelloFirefox_63} - testUTLSHandshakeClientTLS13_AES_128_GCM_SHA256(t, helloID) - testUTLSHandshakeClientTLS13_AES_256_GCM_SHA384(t, helloID) - testUTLSHandshakeClientTLS13_CHACHA20_POLY1305_SHA256(t, helloID) + testUTLSHandshakeClientTLS13_AES_128_GCM_SHA256(t, hello) + testUTLSHandshakeClientTLS13_AES_256_GCM_SHA384(t, hello) + testUTLSHandshakeClientTLS13_CHACHA20_POLY1305_SHA256(t, hello) - testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, helloID) + testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello) - testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, helloID) - testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, helloID) + testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello) + testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello) - //testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, helloID) TODO: enable when OpenSSL supports it - testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, helloID) + //testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, hello) TODO: enable when OpenSSL supports it + testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, hello) - testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t, helloID) - testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t, helloID) + testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t, hello) + testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t, hello) - testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, helloID) + testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello) } func TestUTLSHandshakeClientParrotFirefox_55(t *testing.T) { - helloID := HelloFirefox_55 + hello := &helloID{HelloFirefox_55} - testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, helloID) + testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t, hello) - testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, helloID) - testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, helloID) + testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t, hello) + testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t, hello) - //testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, helloID) TODO: enable when OpenSSL supports it - testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, helloID) + //testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t, hello) TODO: enable when OpenSSL supports it + testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t, hello) - testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t, helloID) - testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t, helloID) + testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t, hello) + testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t, hello) - testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, helloID) - testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, helloID) + testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t, hello) + testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t, hello) } func TestUTLSHandshakeClientParrotChrome_58_setclienthello(t *testing.T) { - helloID := HelloChrome_58 + hello := &helloID{HelloChrome_58} config := getUTLSTestConfig() opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256" test := &clientTest{ - name: "UTLS-setclienthello-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - config: config, + name: "UTLS-setclienthello-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } // tests consistency of fingerprint after HelloRetryRequest // chrome 70 is used, due to only specifying X25519 in keyshare, but being able to generate P-256 curve too // openssl server, configured to use P-256, will send HelloRetryRequest func TestUTLSHelloRetryRequest(t *testing.T) { - helloID := HelloChrome_70 + hello := &helloID{HelloChrome_70} config := testConfig.Clone() config.CurvePreferences = []CurveID{X25519, CurveP256} test := &clientTest{ - name: "UTLS-HelloRetryRequest-" + helloID.Str(), - args: []string{"-cipher", "ECDHE-RSA-AES128-GCM-SHA256", "-curves", "P-256"}, - config: config, + name: "UTLS-HelloRetryRequest-" + hello.helloName(), + args: []string{"-cipher", "ECDHE-RSA-AES128-GCM-SHA256", "-curves", "P-256"}, + config: config, } - runUTLSClientTestTLS13(t, test, helloID) + runUTLSClientTestTLS13(t, test, hello) } func TestUTLSRemoveSNIExtension(t *testing.T) { - helloID := HelloChrome_70 + hello := &helloID{HelloChrome_70} config := getUTLSTestConfig() opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str() + "-OmitSNI", + name: "UTLS-" + opensslCipherName + "-" + hello.helloName() + "-OmitSNI", args: []string{"-cipher", opensslCipherName}, config: config, } - runUTLSClientTestForVersion(t, test, "TLSv12-", "-tls1_2", helloID, true) + runUTLSClientTestForVersion(t, test, "TLSv12-", "-tls1_2", hello, true) } /* @@ -194,192 +216,192 @@ func getUTLSTestConfig() *Config { return testUTLSConfig } -func testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_RSA_AES128_CBC_SHA(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "ECDHE-RSA-AES128-SHA" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_RSA_AES256_CBC_SHA(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "ECDHE-RSA-AES256-SHA" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_ECDSA_AES128_CBC_SHA(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "ECDHE-ECDSA-AES128-SHA" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - cert: testECDSACertificate, - key: testECDSAPrivateKey, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + cert: testECDSACertificate, + key: testECDSAPrivateKey, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_ECDSA_AES256_CBC_SHA(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "ECDHE-ECDSA-AES256-SHA" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - cert: testECDSACertificate, - key: testECDSAPrivateKey, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + cert: testECDSACertificate, + key: testECDSAPrivateKey, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientRSA_AES128_GCM_SHA256(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "AES128-GCM-SHA256" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_ECDSA_AES128_GCM_SHA256(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "ECDHE-ECDSA-AES128-GCM-SHA256" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - cert: testECDSACertificate, - key: testECDSAPrivateKey, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + cert: testECDSACertificate, + key: testECDSAPrivateKey, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_RSA_AES128_GCM_SHA256(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_ECDSA_AES256_GCM_SHA256(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "ECDHE-ECDSA-AES256-GCM-SHA256" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - cert: testECDSACertificate, - key: testECDSAPrivateKey, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + cert: testECDSACertificate, + key: testECDSAPrivateKey, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_RSA_AES256_GCM_SHA256(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientTLS13_AES_128_GCM_SHA256(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientTLS13_AES_128_GCM_SHA256(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "TLS_AES_128_GCM_SHA256" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-ciphersuites", opensslCipherName}, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-ciphersuites", opensslCipherName}, + config: config, } - runUTLSClientTestTLS13(t, test, helloID) + runUTLSClientTestTLS13(t, test, hello) } -func testUTLSHandshakeClientTLS13_AES_256_GCM_SHA384(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientTLS13_AES_256_GCM_SHA384(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "TLS_AES_256_GCM_SHA384" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-ciphersuites", opensslCipherName}, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-ciphersuites", opensslCipherName}, + config: config, } - runUTLSClientTestTLS13(t, test, helloID) + runUTLSClientTestTLS13(t, test, hello) } -func testUTLSHandshakeClientTLS13_CHACHA20_POLY1305_SHA256(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientTLS13_CHACHA20_POLY1305_SHA256(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() opensslCipherName := "TLS_CHACHA20_POLY1305_SHA256" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-ciphersuites", opensslCipherName}, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-ciphersuites", opensslCipherName}, + config: config, } - runUTLSClientTestTLS13(t, test, helloID) + runUTLSClientTestTLS13(t, test, hello) } -func testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_RSA_WITH_CHACHA20_POLY1305(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() config.CipherSuites = []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305} opensslCipherName := "ECDHE-RSA-CHACHA20-POLY1305" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - config: config, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + config: config, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t *testing.T, helloID ClientHelloID) { +func testUTLSHandshakeClientECDHE_ECDSA_WITH_CHACHA20_POLY1305(t *testing.T, hello helloStrategy) { config := getUTLSTestConfig() config.CipherSuites = []uint16{TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305} opensslCipherName := "ECDHE-ECDSA-CHACHA20-POLY1305" test := &clientTest{ - name: "UTLS-" + opensslCipherName + "-" + helloID.Str(), - args: []string{"-cipher", opensslCipherName}, - config: config, - cert: testECDSACertificate, - key: testECDSAPrivateKey, + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + config: config, + cert: testECDSACertificate, + key: testECDSAPrivateKey, } - runUTLSClientTestTLS12(t, test, helloID) + runUTLSClientTestTLS12(t, test, hello) } -func runUTLSClientTestForVersion(t *testing.T, template *clientTest, prefix, option string, helloID ClientHelloID, omitSNI bool) { +func runUTLSClientTestForVersion(t *testing.T, template *clientTest, prefix, option string, hello helloStrategy, omitSNI bool) { test := *template test.name = prefix + test.name if len(test.args) == 0 { @@ -387,18 +409,18 @@ func runUTLSClientTestForVersion(t *testing.T, template *clientTest, prefix, opt } test.args = append([]string(nil), test.args...) test.args = append(test.args, option) - test.runUTLS(t, *update, helloID, omitSNI) + test.runUTLS(t, *update, hello, omitSNI) } -func runUTLSClientTestTLS12(t *testing.T, template *clientTest, helloID ClientHelloID) { - runUTLSClientTestForVersion(t, template, "TLSv12-", "-tls1_2", helloID, false) +func runUTLSClientTestTLS12(t *testing.T, template *clientTest, hello helloStrategy) { + runUTLSClientTestForVersion(t, template, "TLSv12-", "-tls1_2", hello, false) } -func runUTLSClientTestTLS13(t *testing.T, template *clientTest, helloID ClientHelloID) { - runUTLSClientTestForVersion(t, template, "TLSv13-", "-tls1_3", helloID, false) +func runUTLSClientTestTLS13(t *testing.T, template *clientTest, hello helloStrategy) { + runUTLSClientTestForVersion(t, template, "TLSv13-", "-tls1_3", hello, false) } -func (test *clientTest) runUTLS(t *testing.T, write bool, helloID ClientHelloID, omitSNIExtension bool) { +func (test *clientTest) runUTLS(t *testing.T, write bool, hello helloStrategy, omitSNIExtension bool) { checkOpenSSLVersion(t) var clientConn, serverConn net.Conn @@ -423,7 +445,20 @@ func (test *clientTest) runUTLS(t *testing.T, write bool, helloID ClientHelloID, t.Error("Explicit config is mandatory") return } - client := UClient(clientConn, config, helloID) + + var client *UConn + switch h := hello.(type) { + case *helloID: + client = UClient(clientConn, config, h.id) + case *helloSpec: + client = UClient(clientConn, config, HelloCustom) + if err := client.ApplyPreset(h.spec); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + return + } + default: + panic("unknown helloStrategy") + } if omitSNIExtension { if err := client.RemoveSNIExtension(); err != nil { diff --git a/u_fingerprinter.go b/u_fingerprinter.go new file mode 100644 index 0000000..ec1df89 --- /dev/null +++ b/u_fingerprinter.go @@ -0,0 +1,360 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tls + +import ( + "errors" + "fmt" + "strings" + + "golang.org/x/crypto/cryptobyte" +) + +// Fingerprinter is a struct largely for holding options for the FingerprintClientHello func +type Fingerprinter struct { + // KeepPSK will ensure that the PreSharedKey extension is passed along into the resulting ClientHelloSpec as-is + KeepPSK bool + // AllowBluntMimicry will ensure that unknown extensions are + // passed along into the resulting ClientHelloSpec as-is + // It will not ensure that the PSK is passed along, if you require that, use KeepPSK + // WARNING: there could be numerous subtle issues with ClientHelloSpecs + // that are generated with this flag which could compromise security and/or mimicry + AllowBluntMimicry bool + // AlwaysAddPadding will always add a UtlsPaddingExtension with BoringPaddingStyle + // at the end of the extensions list if it isn't found in the fingerprinted hello. + // This could be useful in scenarios where the hello you are fingerprinting does not + // have any padding, but you suspect that other changes you make to the final hello + // (including things like different SNI lengths) would cause padding to be necessary + AlwaysAddPadding bool +} + +// FingerprintClientHello returns a ClientHelloSpec which is based on the +// ClientHello that is passed in as the data argument +// +// If the ClientHello passed in has extensions that are not recognized or cannot be handled +// it will return a non-nil error and a nil *ClientHelloSpec value +// +// The data should be the full tls record, including the record type/version/length header +// as well as the handshake type/length/version header +// https://tools.ietf.org/html/rfc5246#section-6.2 +// https://tools.ietf.org/html/rfc5246#section-7.4 +func (f *Fingerprinter) FingerprintClientHello(data []byte) (*ClientHelloSpec, error) { + clientHelloSpec := &ClientHelloSpec{} + s := cryptobyte.String(data) + + var contentType uint8 + var recordVersion uint16 + if !s.ReadUint8(&contentType) || // record type + !s.ReadUint16(&recordVersion) || !s.Skip(2) { // record version and length + return nil, errors.New("unable to read record type, version, and length") + } + + if recordType(contentType) != recordTypeHandshake { + return nil, errors.New("record is not a handshake") + } + + var handshakeVersion uint16 + var handshakeType uint8 + + if !s.ReadUint8(&handshakeType) || !s.Skip(3) || // message type and 3 byte length + !s.ReadUint16(&handshakeVersion) || !s.Skip(32) { // 32 byte random + return nil, errors.New("unable to read handshake message type, length, and random") + } + + if handshakeType != typeClientHello { + return nil, errors.New("handshake message is not a ClientHello") + } + + clientHelloSpec.TLSVersMin = recordVersion + clientHelloSpec.TLSVersMax = handshakeVersion + + var ignoredSessionID cryptobyte.String + if !s.ReadUint8LengthPrefixed(&ignoredSessionID) { + return nil, errors.New("unable to read session id") + } + + var cipherSuitesBytes cryptobyte.String + if !s.ReadUint16LengthPrefixed(&cipherSuitesBytes) { + return nil, errors.New("unable to read ciphersuites") + } + cipherSuites := []uint16{} + for !cipherSuitesBytes.Empty() { + var suite uint16 + if !cipherSuitesBytes.ReadUint16(&suite) { + return nil, errors.New("unable to read ciphersuite") + } + cipherSuites = append(cipherSuites, unGREASEUint16(suite)) + } + clientHelloSpec.CipherSuites = cipherSuites + + if !readUint8LengthPrefixed(&s, &clientHelloSpec.CompressionMethods) { + return nil, errors.New("unable to read compression methods") + } + + if s.Empty() { + // ClientHello is optionally followed by extension data + return clientHelloSpec, nil + } + + var extensions cryptobyte.String + if !s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() { + return nil, errors.New("unable to read extensions data") + } + + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) || + !extensions.ReadUint16LengthPrefixed(&extData) { + return nil, errors.New("unable to read extension data") + } + + switch extension { + case extensionServerName: + // RFC 6066, Section 3 + var nameList cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&nameList) || nameList.Empty() { + return nil, errors.New("unable to read server name extension data") + } + var serverName string + for !nameList.Empty() { + var nameType uint8 + var serverNameBytes cryptobyte.String + if !nameList.ReadUint8(&nameType) || + !nameList.ReadUint16LengthPrefixed(&serverNameBytes) || + serverNameBytes.Empty() { + return nil, errors.New("unable to read server name extension data") + } + if nameType != 0 { + continue + } + if len(serverName) != 0 { + return nil, errors.New("multiple names of the same name_type in server name extension are prohibited") + } + serverName = string(serverNameBytes) + if strings.HasSuffix(serverName, ".") { + return nil, errors.New("SNI value may not include a trailing dot") + } + + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SNIExtension{}) + + } + case extensionNextProtoNeg: + // draft-agl-tls-nextprotoneg-04 + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &NPNExtension{}) + + case extensionStatusRequest: + // RFC 4366, Section 3.6 + var statusType uint8 + var ignored cryptobyte.String + if !extData.ReadUint8(&statusType) || + !extData.ReadUint16LengthPrefixed(&ignored) || + !extData.ReadUint16LengthPrefixed(&ignored) { + return nil, errors.New("unable to read status request extension data") + } + + if statusType == statusTypeOCSP { + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &StatusRequestExtension{}) + } else { + return nil, errors.New("status request extension statusType is not statusTypeOCSP") + } + + case extensionSupportedCurves: + // RFC 4492, sections 5.1.1 and RFC 8446, Section 4.2.7 + var curvesBytes cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&curvesBytes) || curvesBytes.Empty() { + return nil, errors.New("unable to read supported curves extension data") + } + curves := []CurveID{} + for !curvesBytes.Empty() { + var curve uint16 + if !curvesBytes.ReadUint16(&curve) { + return nil, errors.New("unable to read supported curves extension data") + } + curves = append(curves, CurveID(unGREASEUint16(curve))) + } + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedCurvesExtension{curves}) + + case extensionSupportedPoints: + // RFC 4492, Section 5.1.2 + supportedPoints := []uint8{} + if !readUint8LengthPrefixed(&extData, &supportedPoints) || + len(supportedPoints) == 0 { + return nil, errors.New("unable to read supported points extension data") + } + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedPointsExtension{supportedPoints}) + + case extensionSessionTicket: + // RFC 5077, Section 3.2 + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SessionTicketExtension{}) + + case extensionSignatureAlgorithms: + // RFC 5246, Section 7.4.1.4.1 + var sigAndAlgs cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&sigAndAlgs) || sigAndAlgs.Empty() { + return nil, errors.New("unable to read signature algorithms extension data") + } + supportedSignatureAlgorithms := []SignatureScheme{} + for !sigAndAlgs.Empty() { + var sigAndAlg uint16 + if !sigAndAlgs.ReadUint16(&sigAndAlg) { + return nil, errors.New("unable to read signature algorithms extension data") + } + supportedSignatureAlgorithms = append( + supportedSignatureAlgorithms, SignatureScheme(sigAndAlg)) + } + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SignatureAlgorithmsExtension{supportedSignatureAlgorithms}) + + case extensionSignatureAlgorithmsCert: + // RFC 8446, Section 4.2.3 + if f.AllowBluntMimicry { + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData}) + } else { + return nil, errors.New("unsupported extension SignatureAlgorithmsCert") + } + + case extensionRenegotiationInfo: + // RFC 5746, Section 3.2 + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &RenegotiationInfoExtension{RenegotiateOnceAsClient}) + + case extensionALPN: + // RFC 7301, Section 3.1 + var protoList cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&protoList) || protoList.Empty() { + return nil, errors.New("unable to read ALPN extension data") + } + alpnProtocols := []string{} + for !protoList.Empty() { + var proto cryptobyte.String + if !protoList.ReadUint8LengthPrefixed(&proto) || proto.Empty() { + return nil, errors.New("unable to read ALPN extension data") + } + alpnProtocols = append(alpnProtocols, string(proto)) + + } + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &ALPNExtension{alpnProtocols}) + + case extensionSCT: + // RFC 6962, Section 3.3.1 + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SCTExtension{}) + + case extensionSupportedVersions: + // RFC 8446, Section 4.2.1 + var versList cryptobyte.String + if !extData.ReadUint8LengthPrefixed(&versList) || versList.Empty() { + return nil, errors.New("unable to read supported versions extension data") + } + supportedVersions := []uint16{} + for !versList.Empty() { + var vers uint16 + if !versList.ReadUint16(&vers) { + return nil, errors.New("unable to read supported versions extension data") + } + supportedVersions = append(supportedVersions, unGREASEUint16(vers)) + } + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &SupportedVersionsExtension{supportedVersions}) + // If SupportedVersionsExtension is present, use that instead of record+handshake versions + clientHelloSpec.TLSVersMin = 0 + clientHelloSpec.TLSVersMax = 0 + + case extensionKeyShare: + // RFC 8446, Section 4.2.8 + var clientShares cryptobyte.String + if !extData.ReadUint16LengthPrefixed(&clientShares) { + return nil, errors.New("unable to read key share extension data") + } + keyShares := []KeyShare{} + for !clientShares.Empty() { + var ks KeyShare + var group uint16 + if !clientShares.ReadUint16(&group) || + !readUint16LengthPrefixed(&clientShares, &ks.Data) || + len(ks.Data) == 0 { + return nil, errors.New("unable to read key share extension data") + } + ks.Group = CurveID(unGREASEUint16(group)) + // if not GREASE, key share data will be discarded as it should + // be generated per connection + if ks.Group != GREASE_PLACEHOLDER { + ks.Data = nil + } + keyShares = append(keyShares, ks) + } + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &KeyShareExtension{keyShares}) + + case extensionPSKModes: + // RFC 8446, Section 4.2.9 + // TODO: PSK Modes have their own form of GREASE-ing which is not currently implemented + // the current functionality will NOT re-GREASE/re-randomize these values when using a fingerprinted spec + // https://github.com/refraction-networking/utls/pull/58#discussion_r522354105 + // https://tools.ietf.org/html/draft-ietf-tls-grease-01#section-2 + pskModes := []uint8{} + if !readUint8LengthPrefixed(&extData, &pskModes) { + return nil, errors.New("unable to read PSK extension data") + } + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &PSKKeyExchangeModesExtension{pskModes}) + + case utlsExtensionExtendedMasterSecret: + // https://tools.ietf.org/html/rfc7627 + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsExtendedMasterSecretExtension{}) + + case utlsExtensionPadding: + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}) + + case fakeExtensionChannelID, fakeCertCompressionAlgs, fakeRecordSizeLimit: + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData}) + + case extensionPreSharedKey: + // RFC 8446, Section 4.2.11 + if f.KeepPSK { + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData}) + } else { + return nil, errors.New("unsupported extension PreSharedKey") + } + + case extensionCookie: + // RFC 8446, Section 4.2.2 + if f.AllowBluntMimicry { + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData}) + } else { + return nil, errors.New("unsupported extension Cookie") + } + + case extensionEarlyData: + // RFC 8446, Section 4.2.10 + if f.AllowBluntMimicry { + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData}) + } else { + return nil, errors.New("unsupported extension EarlyData") + } + + default: + if isGREASEUint16(extension) { + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsGREASEExtension{unGREASEUint16(extension), extData}) + } else if f.AllowBluntMimicry { + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &GenericExtension{extension, extData}) + } else { + return nil, fmt.Errorf("unsupported extension %#x", extension) + } + + continue + } + } + + if f.AlwaysAddPadding { + alreadyHasPadding := false + for _, ext := range clientHelloSpec.Extensions { + if _, ok := ext.(*UtlsPaddingExtension); ok { + alreadyHasPadding = true + break + } + } + if !alreadyHasPadding { + clientHelloSpec.Extensions = append(clientHelloSpec.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}) + } + } + + return clientHelloSpec, nil +} diff --git a/u_fingerprinter_test.go b/u_fingerprinter_test.go new file mode 100644 index 0000000..eb1250e --- /dev/null +++ b/u_fingerprinter_test.go @@ -0,0 +1,716 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tls + +import ( + "bytes" + "encoding/hex" + "fmt" + "io/ioutil" + "net" + "reflect" + "testing" +) + +func assertEquality(t *testing.T, fieldName string, expected, actual interface{}) { + if kActual, ok := actual.(KeyShare); ok { + kExpected := expected.(KeyShare) + assertEquality(t, fieldName, kExpected.Group, kActual.Group) + return + } + + if fieldName == "SupportedCurves" || fieldName == "KeyShares" { + cExpected := expected.(CurveID) + cActual := actual.(CurveID) + if isGREASEUint16(uint16(cExpected)) && isGREASEUint16(uint16(cActual)) { + return + } + } + + if fieldName == "SupportedVersions" || fieldName == "CipherSuites" { + cExpected := expected.(uint16) + cActual := actual.(uint16) + if isGREASEUint16(cExpected) && isGREASEUint16(cActual) { + return + } + } + + if expected != actual { + t.Errorf("%v fields not equal, expected: %v, got: %v", fieldName, expected, actual) + } +} + +func compareClientHelloFields(t *testing.T, fieldName string, expected, actual *ClientHelloMsg) { + rExpected := reflect.ValueOf(expected) + if rExpected.Kind() != reflect.Ptr || rExpected.Elem().Kind() != reflect.Struct { + t.Errorf("Error using reflect to compare Hello fields") + } + rActual := reflect.ValueOf(actual) + if rActual.Kind() != reflect.Ptr || rActual.Elem().Kind() != reflect.Struct { + t.Errorf("Error using reflect to compare Hello fields") + } + + rExpected = rExpected.Elem() + rActual = rActual.Elem() + + fExpected := rExpected.FieldByName(fieldName) + fActual := rActual.FieldByName(fieldName) + if !(fExpected.IsValid() && fActual.IsValid()) { + t.Errorf("Error using reflect to lookup Hello field name: %v", fieldName) + } + + if fExpected.Kind() == reflect.Slice { + sExpected := fExpected.Slice(0, fExpected.Len()) + sActual := fActual.Slice(0, fActual.Len()) + + if sExpected.Len() != sActual.Len() { + t.Errorf("%v fields slice length not equal, expected: %v, got: %v", fieldName, fExpected, fActual) + } + + for i := 0; i < sExpected.Len(); i++ { + assertEquality(t, fieldName, sExpected.Index(i).Interface(), sActual.Index(i).Interface()) + } + } else { + assertEquality(t, fieldName, fExpected.Interface(), fActual.Interface()) + } +} + +func checkUTLSExtensionsEquality(t *testing.T, expected, actual TLSExtension) { + if expectedGrease, ok := expected.(*UtlsGREASEExtension); ok { + if actualGrease, ok := actual.(*UtlsGREASEExtension); ok { + if bytes.Equal(expectedGrease.Body, actualGrease.Body) { + return + } + } + } + + if expected.Len() != actual.Len() { + t.Errorf("extension types length not equal\nexpected: %#v\ngot: %#v", expected, actual) + } + + actualBytes, err := ioutil.ReadAll(actual) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + expectedBytes, err := ioutil.ReadAll(expected) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + logInequality := func() { + t.Errorf("extensions not equal\nexpected: %#v\nbytes:%#x\ngot: %#v\nbytes: %#x", expected, expectedBytes, actual, actualBytes) + } + + if !bytes.Equal(expectedBytes, actualBytes) { + // handle all the cases where GREASE or other factors can cause byte unalignment + + // at this point concrete types must match + expectedType := reflect.TypeOf(expected) + actualType := reflect.TypeOf(actual) + if expectedType != actualType { + t.Errorf("extensions not equal\nexpected: %#v\nbytes:%#x\ngot: %#v\nbytes: %#x", expected, expectedBytes, actual, actualBytes) + return + } + + switch expectedExtension := expected.(type) { + case *SupportedCurvesExtension: + actualExtension := expected.(*SupportedCurvesExtension) + for i, expectedCurve := range expectedExtension.Curves { + actualCurve := actualExtension.Curves[i] + if expectedCurve == actualCurve { + continue + } + if isGREASEUint16(uint16(expectedCurve)) && isGREASEUint16(uint16(actualCurve)) { + continue + } + logInequality() + return + } + case *KeyShareExtension: + actualExtension := expected.(*KeyShareExtension) + for i, expectedKeyShare := range expectedExtension.KeyShares { + actualKeyShare := actualExtension.KeyShares[i] + // KeyShare data is unique per connection + if actualKeyShare.Group == expectedKeyShare.Group { + continue + } + if isGREASEUint16(uint16(expectedKeyShare.Group)) && isGREASEUint16(uint16(actualKeyShare.Group)) { + continue + } + logInequality() + return + } + case *SupportedVersionsExtension: + actualExtension := expected.(*SupportedVersionsExtension) + for i, expectedVersion := range expectedExtension.Versions { + actualVersion := actualExtension.Versions[i] + if isGREASEUint16(expectedVersion) && isGREASEUint16(actualVersion) || actualVersion == expectedVersion { + continue + } + logInequality() + return + } + default: + logInequality() + return + } + } +} + +// Conn.vers is sometimes left to zero which is unacceptable to uTLS' SetTLSVers +// https://github.com/refraction-networking/utls/blob/f7e7360167ed2903ef12898634512b66f8c3aad0/u_conn.go#L564-L566 +// https://github.com/refraction-networking/utls/blob/f7e7360167ed2903ef12898634512b66f8c3aad0/conn.go#L945-L948 +func createMinTLSVersion(vers uint16) uint16 { + if vers == 0 { + return VersionTLS10 + } + return vers +} + +// prependRecordHeader prepends a record header to a handshake messsage +// if attempting to mimic an existing connection the minTLSVersion can be found +// in the Conn.vers field +func prependRecordHeader(hello []byte, minTLSVersion uint16) []byte { + l := len(hello) + if minTLSVersion == 0 { + minTLSVersion = VersionTLS10 + } + header := []byte{ + uint8(recordTypeHandshake), // type + uint8(minTLSVersion >> 8 & 0xff), uint8(minTLSVersion & 0xff), // record version is the minimum supported + uint8(l >> 8 & 0xff), uint8(l & 0xff), // length + } + return append(header, hello...) +} + +func checkUTLSFingerPrintClientHello(t *testing.T, clientHelloID ClientHelloID, serverName string) { + uconn := UClient(&net.TCPConn{}, &Config{ServerName: serverName}, clientHelloID) + if err := uconn.BuildHandshakeState(); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + generatedUConn := UClient(&net.TCPConn{}, &Config{ServerName: "foobar"}, HelloCustom) + fingerprinter := &Fingerprinter{} + minTLSVers := createMinTLSVersion(uconn.vers) + generatedSpec, err := fingerprinter.FingerprintClientHello(prependRecordHeader(uconn.HandshakeState.Hello.Raw, minTLSVers)) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + if err := generatedUConn.ApplyPreset(generatedSpec); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + if err := generatedUConn.BuildHandshakeState(); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + if len(uconn.HandshakeState.Hello.Raw) != len(generatedUConn.HandshakeState.Hello.Raw) { + t.Errorf("UConn from fingerprint has %d length, should have %d", len(generatedUConn.HandshakeState.Hello.Raw), len(uconn.HandshakeState.Hello.Raw)) + } + + // We can't effectively check the extensions on randomized client hello ids + if !(clientHelloID == HelloRandomized || clientHelloID == HelloRandomizedALPN || clientHelloID == HelloRandomizedNoALPN) { + for i, originalExtension := range uconn.Extensions { + if _, ok := originalExtension.(*UtlsPaddingExtension); ok { + // We can't really compare padding extensions in this way + continue + } + + generatedExtension := generatedUConn.Extensions[i] + checkUTLSExtensionsEquality(t, originalExtension, generatedExtension) + } + } + + fieldsToTest := []string{ + "Vers", "CipherSuites", "CompressionMethods", "NextProtoNeg", "ServerName", "OcspStapling", "Scts", "SupportedCurves", + "SupportedPoints", "TicketSupported", "SupportedSignatureAlgorithms", "SecureRenegotiation", "SecureRenegotiationSupported", "AlpnProtocols", + "SupportedSignatureAlgorithmsCert", "SupportedVersions", "KeyShares", "EarlyData", "PskModes", "PskIdentities", "PskBinders", + } + + for _, field := range fieldsToTest { + compareClientHelloFields(t, field, uconn.HandshakeState.Hello, generatedUConn.HandshakeState.Hello) + } +} + +func TestUTLSFingerprintClientHello(t *testing.T) { + clientHellosToTest := []ClientHelloID{ + HelloChrome_58, HelloChrome_70, HelloChrome_83, HelloFirefox_55, HelloFirefox_63, HelloIOS_11_1, HelloIOS_12_1, HelloRandomized, HelloRandomizedALPN, HelloRandomizedNoALPN} + + serverNames := []string{"foobar"} + + for _, clientHello := range clientHellosToTest { + for _, serverName := range serverNames { + t.Logf("checking fingerprint generated client hello spec against %v and server name: %v", clientHello, serverName) + checkUTLSFingerPrintClientHello(t, clientHello, "foobar") + } + } +} + +func TestUTLSFingerprintClientHelloBluntMimicry(t *testing.T) { + serverName := "foobar" + var extensionId uint16 = 0xfeed + extensionData := []byte("random data") + + specWithGeneric, err := utlsIdToSpec(HelloChrome_Auto) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + specWithGeneric.Extensions = append(specWithGeneric.Extensions, &GenericExtension{extensionId, extensionData}) + + uconn := UClient(&net.TCPConn{}, &Config{ServerName: serverName}, HelloCustom) + + if err := uconn.ApplyPreset(&specWithGeneric); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + if err := uconn.BuildHandshakeState(); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + f := &Fingerprinter{} + minTLSVers := createMinTLSVersion(uconn.vers) + _, err = f.FingerprintClientHello(prependRecordHeader(uconn.HandshakeState.Hello.Raw, minTLSVers)) + if err == nil { + t.Errorf("expected error generating spec from client hello with GenericExtension") + } + + f = &Fingerprinter{AllowBluntMimicry: true} + generatedSpec, err := f.FingerprintClientHello(prependRecordHeader(uconn.HandshakeState.Hello.Raw, minTLSVers)) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + for _, ext := range generatedSpec.Extensions { + if genericExtension, ok := (ext).(*GenericExtension); ok { + if genericExtension.Id == extensionId && bytes.Equal(genericExtension.Data, extensionData) { + return + } + } + } + t.Errorf("generated ClientHelloSpec with BluntMimicry did not correctly carry over generic extension") +} + +func TestUTLSFingerprintClientHelloAlwaysAddPadding(t *testing.T) { + serverName := "foobar" + + specWithoutPadding, err := utlsIdToSpec(HelloIOS_12_1) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + specWithPadding, err := utlsIdToSpec(HelloChrome_83) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + uconnWithoutPadding := UClient(&net.TCPConn{}, &Config{ServerName: serverName}, HelloCustom) + uconnWithPadding := UClient(&net.TCPConn{}, &Config{ServerName: serverName}, HelloCustom) + + if err := uconnWithoutPadding.ApplyPreset(&specWithoutPadding); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + if err := uconnWithoutPadding.BuildHandshakeState(); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + if err := uconnWithPadding.ApplyPreset(&specWithPadding); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + if err := uconnWithPadding.BuildHandshakeState(); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + f := &Fingerprinter{} + minTLSVersWithoutPadding := createMinTLSVersion(uconnWithoutPadding.vers) + fingerprintedWithoutPadding, err := f.FingerprintClientHello(prependRecordHeader(uconnWithoutPadding.HandshakeState.Hello.Raw, minTLSVersWithoutPadding)) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + for _, ext := range fingerprintedWithoutPadding.Extensions { + if _, ok := ext.(*UtlsPaddingExtension); ok { + t.Errorf("padding extension should not be present on fingerprinted ClientHelloSpec without AlwaysAddPadding set") + break + } + } + + f = &Fingerprinter{AlwaysAddPadding: true} + generatedSpec, err := f.FingerprintClientHello(prependRecordHeader(uconnWithoutPadding.HandshakeState.Hello.Raw, minTLSVersWithoutPadding)) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + hasPadding := false + for _, ext := range generatedSpec.Extensions { + if _, ok := ext.(*UtlsPaddingExtension); ok { + hasPadding = true + break + } + } + if !hasPadding { + t.Errorf("expected padding extension on fingerprinted ClientHelloSpec with AlwaysAddPadding set") + } + + f = &Fingerprinter{AlwaysAddPadding: true} + minTLSVersWithPadding := createMinTLSVersion(uconnWithPadding.vers) + generatedSpec, err = f.FingerprintClientHello(prependRecordHeader(uconnWithPadding.HandshakeState.Hello.Raw, minTLSVersWithPadding)) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + hasPadding = false + for _, ext := range generatedSpec.Extensions { + if _, ok := ext.(*UtlsPaddingExtension); ok { + if hasPadding { + t.Errorf("found double padding extension on fingerprinted ClientHelloSpec with AlwaysAddPadding set") + } + + hasPadding = true + } + } + if !hasPadding { + t.Errorf("expected padding extension on fingerprinted ClientHelloSpec with AlwaysAddPadding set") + } +} + +func TestUTLSFingerprintClientHelloKeepPSK(t *testing.T) { + // TLSv1.3 Record Layer: Handshake Protocol: Client Hello + // Content Type: Handshake (22) + // Version: TLS 1.0 (0x0301) + // Length: 576 + // Handshake Protocol: Client Hello + // Handshake Type: Client Hello (1) + // Length: 572 + // Version: TLS 1.2 (0x0303) + // Random: 5cef5aa9122008e37f0f74d717cd4ae0f745daba4292e6fb… + // Session ID Length: 32 + // Session ID: 8c4aa23444084eeb70097efe0b8f6e3a56c717abd67505c9… + // Cipher Suites Length: 32 + // Cipher Suites (16 suites) + // Compression Methods Length: 1 + // Compression Methods (1 method) + // Extensions Length: 467 + // Extension: Reserved (GREASE) (len=0) + // Type: Reserved (GREASE) (14906) + // Length: 0 + // Data: + // Extension: server_name (len=22) + // Type: server_name (0) + // Length: 22 + // Server Name Indication extension + // Server Name list length: 20 + // Server Name Type: host_name (0) + // Server Name length: 17 + // Server Name: edgeapi.slack.com + // Extension: extended_master_secret (len=0) + // Type: extended_master_secret (23) + // Length: 0 + // Extension: renegotiation_info (len=1) + // Type: renegotiation_info (65281) + // Length: 1 + // Renegotiation Info extension + // Renegotiation info extension length: 0 + // Extension: supported_groups (len=10) + // Type: supported_groups (10) + // Length: 10 + // Supported Groups List Length: 8 + // Supported Groups (4 groups) + // Supported Group: Reserved (GREASE) (0xdada) + // Supported Group: x25519 (0x001d) + // Supported Group: secp256r1 (0x0017) + // Supported Group: secp384r1 (0x0018) + // Extension: ec_point_formats (len=2) + // Type: ec_point_formats (11) + // Length: 2 + // EC point formats Length: 1 + // Elliptic curves point formats (1) + // Extension: session_ticket (len=0) + // Type: session_ticket (35) + // Length: 0 + // Data (0 bytes) + // Extension: application_layer_protocol_negotiation (len=14) + // Type: application_layer_protocol_negotiation (16) + // Length: 14 + // ALPN Extension Length: 12 + // ALPN Protocol + // ALPN string length: 2 + // ALPN Next Protocol: h2 + // ALPN string length: 8 + // ALPN Next Protocol: http/1.1 + // Extension: status_request (len=5) + // Type: status_request (5) + // Length: 5 + // Certificate Status Type: OCSP (1) + // Responder ID list Length: 0 + // Request Extensions Length: 0 + // Extension: signature_algorithms (len=18) + // Type: signature_algorithms (13) + // Length: 18 + // Signature Hash Algorithms Length: 16 + // Signature Hash Algorithms (8 algorithms) + // Extension: signed_certificate_timestamp (len=0) + // Type: signed_certificate_timestamp (18) + // Length: 0 + // Extension: key_share (len=43) + // Type: key_share (51) + // Length: 43 + // Key Share extension + // Client Key Share Length: 41 + // Key Share Entry: Group: Reserved (GREASE), Key Exchange length: 1 + // Group: Reserved (GREASE) (56026) + // Key Exchange Length: 1 + // Key Exchange: 00 + // Key Share Entry: Group: x25519, Key Exchange length: 32 + // Group: x25519 (29) + // Key Exchange Length: 32 + // Key Exchange: e35e636d4e2dcd5f39309170285dab92dbe81fefe4926826… + // Extension: psk_key_exchange_modes (len=2) + // Type: psk_key_exchange_modes (45) + // Length: 2 + // PSK Key Exchange Modes Length: 1 + // PSK Key Exchange Mode: PSK with (EC)DHE key establishment (psk_dhe_ke) (1) + // Extension: supported_versions (len=11) + // Type: supported_versions (43) + // Length: 11 + // Supported Versions length: 10 + // Supported Version: Unknown (0x2a2a) + // Supported Version: TLS 1.3 (0x0304) + // Supported Version: TLS 1.2 (0x0303) + // Supported Version: TLS 1.1 (0x0302) + // Supported Version: TLS 1.0 (0x0301) + // Extension: compress_certificate (len=3) + // Type: compress_certificate (27) + // Length: 3 + // Algorithms Length: 2 + // Algorithm: brotli (2) + // Extension: Reserved (GREASE) (len=1) + // Type: Reserved (GREASE) (19018) + // Length: 1 + // Data: 00 + // Extension: pre_shared_key (len=267) + // Type: pre_shared_key (41) + // Length: 267 + // Pre-Shared Key extension + + byteString := []byte("16030102400100023c03035cef5aa9122008e37f0f74d717cd4ae0f745daba4292e6fbca3cd5bf9123498f208c4aa23444084eeb70097efe0b8f6e3a56c717abd67505c950aab314de59bd8f00204a4a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035010001d33a3a0000000000160014000011656467656170692e736c61636b2e636f6d00170000ff01000100000a000a0008dada001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d0012001004030804040105030805050108060601001200000033002b0029dada000100001d0020e35e636d4e2dcd5f39309170285dab92dbe81fefe4926826cec1ef881321687e002d00020101002b000b0a2a2a0304030303020301001b00030200024a4a0001000029010b00e600e017fab59672c1966ae78fc4dacd7efb42e735de956e3f96d342bb8e63a5233ce21c92d6d75036601d74ccbc3ca0085f3ac2ebbd83da13501ac3c6d612bcb453fb206a39a8112d768bea1976d7c14e6de9aa0ee70ea732554d3c57d1a993f1044a46c1fb371811039ef30582cacf41bd497121d67793b8ee4df7a60d525f7df052fd66cda7f141bb553d9253816752d923ac7c71426179db4f26a7d42f0d65a2dd2dbaafb86fa17b2da23fd57c5064c76551cfda86304051231e4da9e697fedbcb5ae8cb2f6cb92f71164acf2edff5bccc1266cd648a53cc46262eabf40727bcb6958a3d1300212083e99d791672d39919dcb387f2fa7aeee938ec32ecf4b861306f7df4f9a8a746") + + helloBytes := make([]byte, hex.DecodedLen(len(byteString))) + _, err := hex.Decode(helloBytes, byteString) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + return + } + + f := &Fingerprinter{} + _, err = f.FingerprintClientHello(helloBytes) + if err == nil { + t.Errorf("expected error generating spec from client hello with PSK") + } + + f = &Fingerprinter{KeepPSK: true} + generatedSpec, err := f.FingerprintClientHello(helloBytes) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + return + } + + for _, ext := range generatedSpec.Extensions { + if genericExtension, ok := (ext).(*GenericExtension); ok { + if genericExtension.Id == extensionPreSharedKey { + return + } + } + } + t.Errorf("generated ClientHelloSpec with KeepPSK does not include preshared key extension") +} + +func TestUTLSHandshakeClientFingerprintedSpecFromChrome_58(t *testing.T) { + helloID := HelloChrome_58 + serverName := "foobar" + originalConfig := getUTLSTestConfig() + originalConfig.ServerName = serverName + uconn := UClient(&net.TCPConn{}, originalConfig, helloID) + if err := uconn.BuildHandshakeState(); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + f := &Fingerprinter{} + minTLSVers := createMinTLSVersion(uconn.vers) + generatedSpec, err := f.FingerprintClientHello(prependRecordHeader(uconn.HandshakeState.Hello.Raw, minTLSVers)) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + hello := &helloSpec{ + name: fmt.Sprintf("%v-fingerprinted", helloID.Str()), + spec: generatedSpec, + } + + newConfig := getUTLSTestConfig() + newConfig.ServerName = serverName + + opensslCipherName := "ECDHE-RSA-AES128-GCM-SHA256" + test := &clientTest{ + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-cipher", opensslCipherName}, + config: newConfig, + } + + runUTLSClientTestTLS12(t, test, hello) +} + +func TestUTLSHandshakeClientFingerprintedSpecFromChrome_70(t *testing.T) { + helloID := HelloChrome_70 + serverName := "foobar" + originalConfig := getUTLSTestConfig() + originalConfig.ServerName = serverName + + uconn := UClient(&net.TCPConn{}, originalConfig, helloID) + if err := uconn.BuildHandshakeState(); err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + f := &Fingerprinter{} + minTLSVers := createMinTLSVersion(uconn.vers) + generatedSpec, err := f.FingerprintClientHello(prependRecordHeader(uconn.HandshakeState.Hello.Raw, minTLSVers)) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + hello := &helloSpec{ + name: fmt.Sprintf("%v-fingerprinted", helloID.Str()), + spec: generatedSpec, + } + + newConfig := getUTLSTestConfig() + newConfig.ServerName = serverName + + opensslCipherName := "TLS_AES_128_GCM_SHA256" + test := &clientTest{ + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-ciphersuites", opensslCipherName}, + config: newConfig, + } + + runUTLSClientTestTLS13(t, test, hello) +} + +func TestUTLSHandshakeClientFingerprintedSpecFromRaw(t *testing.T) { + // TLSv1.3 Record Layer: Handshake Protocol: Client Hello + // Content Type: Handshake (22) + // Version: TLS 1.0 (0x0301) + // Length: 512 + // Handshake Protocol: Client Hello + // Handshake Type: Client Hello (1) + // Length: 508 + // Version: TLS 1.2 (0x0303) + // Random: 7fd76fa530c24816ea9e4a6cf2e939f2350b9486a7bac58e… + // Session ID Length: 32 + // Session ID: d9b01fc4f4b6fe14fe9ce652442d66588d982cb25913d866… + // Cipher Suites Length: 36 + // Cipher Suites (18 suites) + // Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301) + // Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303) + // Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302) + // Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b) + // Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f) + // Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9) + // Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8) + // Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c) + // Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) + // Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a) + // Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009) + // Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013) + // Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014) + // Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c) + // Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d) + // Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f) + // Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035) + // Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a) + // Compression Methods Length: 1 + // Compression Methods (1 method) + // Extensions Length: 399 + // Extension: server_name (len=34) + // Type: server_name (0) + // Length: 34 + // Server Name Indication extension + // Extension: extended_master_secret (len=0) + // Type: extended_master_secret (23) + // Length: 0 + // Extension: renegotiation_info (len=1) + // Type: renegotiation_info (65281) + // Length: 1 + // Renegotiation Info extension + // Extension: supported_groups (len=14) + // Type: supported_groups (10) + // Length: 14 + // Supported Groups List Length: 12 + // Supported Groups (6 groups) + // Extension: ec_point_formats (len=2) + // Type: ec_point_formats (11) + // Length: 2 + // EC point formats Length: 1 + // Elliptic curves point formats (1) + // Extension: application_layer_protocol_negotiation (len=14) + // Type: application_layer_protocol_negotiation (16) + // Length: 14 + // ALPN Extension Length: 12 + // ALPN Protocol + // Extension: status_request (len=5) + // Type: status_request (5) + // Length: 5 + // Certificate Status Type: OCSP (1) + // Responder ID list Length: 0 + // Request Extensions Length: 0 + // Extension: key_share (len=107) + // Type: key_share (51) + // Length: 107 + // Key Share extension + // Extension: supported_versions (len=5) + // Type: supported_versions (43) + // Length: 5 + // Supported Versions length: 4 + // Supported Version: TLS 1.3 (0x0304) + // Supported Version: TLS 1.2 (0x0303) + // Extension: signature_algorithms (len=24) + // Type: signature_algorithms (13) + // Length: 24 + // Signature Hash Algorithms Length: 22 + // Signature Hash Algorithms (11 algorithms) + // Extension: record_size_limit (len=2) + // Type: record_size_limit (28) + // Length: 2 + // Record Size Limit: 16385 + // Extension: padding (len=143) + // Type: padding (21) + // Length: 143 + // Padding Data: 000000000000000000000000000000000000000000000000… + byteString := []byte("1603010200010001fc03037fd76fa530c24816ea9e4a6cf2e939f2350b9486a7bac58ece5753767fb6112420d9b01fc4f4b6fe14fe9ce652442d66588d982cb25913d866348bde54d3899abe0024130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f0035000a0100018f00000022002000001d70656f706c652d70612e636c69656e7473362e676f6f676c652e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b000201000010000e000c02683208687474702f312e310005000501000000000033006b0069001d002065e566ff33dfbeb012e3b13b87d75612bd0fbc3963673df90afed533dccc9b5400170041047fcc2666d04c31272a2e39905c771a89edf5a71dae301ec2fa0e7bc4d0e06580a0d36324e3dc4f29e200a8905badd11c00daf11588977bf501597dac5fdc55bf002b00050403040303000d0018001604030503060308040805080604010501060102030201001c000240010015008f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + helloBytes := make([]byte, hex.DecodedLen(len(byteString))) + _, err := hex.Decode(helloBytes, byteString) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + return + } + + f := &Fingerprinter{} + generatedSpec, err := f.FingerprintClientHello(helloBytes) + if err != nil { + t.Errorf("got error: %v; expected to succeed", err) + } + + hello := &helloSpec{ + name: "raw-capture-fingerprinted", + spec: generatedSpec, + } + + config := getUTLSTestConfig() + + opensslCipherName := "TLS_AES_128_GCM_SHA256" + test := &clientTest{ + name: "UTLS-" + opensslCipherName + "-" + hello.helloName(), + args: []string{"-ciphersuites", opensslCipherName}, + config: config, + } + + runUTLSClientTestTLS13(t, test, hello) +} diff --git a/u_parrots.go b/u_parrots.go index c0e4975..faa5479 100644 --- a/u_parrots.go +++ b/u_parrots.go @@ -617,6 +617,9 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error { uconn.Extensions = make([]TLSExtension, len(p.Extensions)) copy(uconn.Extensions, p.Extensions) + // Check whether NPN extension actually exists + var haveNPN bool + // reGrease, and point things to each other for _, e := range uconn.Extensions { switch ext := e.(type) { @@ -681,8 +684,15 @@ func (uconn *UConn) ApplyPreset(p *ClientHelloSpec) error { ext.Versions[i] = GetBoringGREASEValue(uconn.greaseSeed, ssl_grease_version) } } + case *NPNExtension: + haveNPN = true } } + + // The default golang behavior in makeClientHello always sets NextProtoNeg if NextProtos is set, + // but NextProtos is also used by ALPN and our spec nmay not actually have a NPN extension + hello.NextProtoNeg = haveNPN + return nil }