mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-02 11:37:36 +03:00
feat: add GREASEEncryptedClientHelloExtension (#266)
* dicttls: update ECH-related entries * wip: GREASE ECH extension * new: GREASE ECH extension * fix: GREASE ECH Read must succeed with io.EOF * new: GREASE ECH multiple payload len * new: parse ECH in EncryptedExtensions * fix: ECHConfig Length always 0 * new: GREASE ECH parrots * new: (*Config).ECHConfigs Add (*Config).ECHConfigs for future full ECH extension. * new: add GREASE ECH example Add an incomplete example of using GREASE ECH extension (Chrome 120 parrot). * fix: invalid httpGetOverConn call fix a problem in old example where httpGetOverConn was called with uTlsConn.HandshakeState.ServerHello.AlpnProtocol, which will not be populated in case TLS 1.3 is used. * new: possible InnerClientHello length
This commit is contained in:
parent
9521fba944
commit
b4de442d02
19 changed files with 925 additions and 51 deletions
2
alert.go
2
alert.go
|
@ -58,6 +58,7 @@ const (
|
||||||
alertUnknownPSKIdentity alert = 115
|
alertUnknownPSKIdentity alert = 115
|
||||||
alertCertificateRequired alert = 116
|
alertCertificateRequired alert = 116
|
||||||
alertNoApplicationProtocol alert = 120
|
alertNoApplicationProtocol alert = 120
|
||||||
|
alertECHRequired alert = 121
|
||||||
)
|
)
|
||||||
|
|
||||||
var alertText = map[alert]string{
|
var alertText = map[alert]string{
|
||||||
|
@ -94,6 +95,7 @@ var alertText = map[alert]string{
|
||||||
alertUnknownPSKIdentity: "unknown PSK identity",
|
alertUnknownPSKIdentity: "unknown PSK identity",
|
||||||
alertCertificateRequired: "certificate required",
|
alertCertificateRequired: "certificate required",
|
||||||
alertNoApplicationProtocol: "no application protocol",
|
alertNoApplicationProtocol: "no application protocol",
|
||||||
|
alertECHRequired: "ECH required",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e alert) String() string {
|
func (e alert) String() string {
|
||||||
|
|
17
common.go
17
common.go
|
@ -306,6 +306,11 @@ type ConnectionState struct {
|
||||||
|
|
||||||
// ekm is a closure exposed via ExportKeyingMaterial.
|
// ekm is a closure exposed via ExportKeyingMaterial.
|
||||||
ekm func(label string, context []byte, length int) ([]byte, error)
|
ekm func(label string, context []byte, length int) ([]byte, error)
|
||||||
|
|
||||||
|
// ECHRetryConfigs contains the ECH retry configurations sent by the server in
|
||||||
|
// EncryptedExtensions message. It is only populated if the server sent the
|
||||||
|
// ech extension in EncryptedExtensions message.
|
||||||
|
ECHRetryConfigs []ECHConfig // [uTLS]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportKeyingMaterial returns length bytes of exported key material in a new
|
// ExportKeyingMaterial returns length bytes of exported key material in a new
|
||||||
|
@ -836,6 +841,17 @@ type Config struct {
|
||||||
// autoSessionTicketKeys is like sessionTicketKeys but is owned by the
|
// autoSessionTicketKeys is like sessionTicketKeys but is owned by the
|
||||||
// auto-rotation logic. See Config.ticketKeys.
|
// auto-rotation logic. See Config.ticketKeys.
|
||||||
autoSessionTicketKeys []ticketKey
|
autoSessionTicketKeys []ticketKey
|
||||||
|
|
||||||
|
// ECHConfigs contains the ECH configurations to be used by the ECH
|
||||||
|
// extension if any.
|
||||||
|
// It could either be distributed by the server in EncryptedExtensions
|
||||||
|
// message or out-of-band.
|
||||||
|
//
|
||||||
|
// If ECHConfigs is nil and an ECH extension is present, GREASEd ECH
|
||||||
|
// extension will be sent.
|
||||||
|
//
|
||||||
|
// If GREASE ECH extension is present, this field will be ignored.
|
||||||
|
ECHConfigs []ECHConfig // [uTLS]
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -921,6 +937,7 @@ func (c *Config) Clone() *Config {
|
||||||
autoSessionTicketKeys: c.autoSessionTicketKeys,
|
autoSessionTicketKeys: c.autoSessionTicketKeys,
|
||||||
|
|
||||||
PreferSkipResumptionOnNilExtension: c.PreferSkipResumptionOnNilExtension, // [UTLS]
|
PreferSkipResumptionOnNilExtension: c.PreferSkipResumptionOnNilExtension, // [UTLS]
|
||||||
|
ECHConfigs: c.ECHConfigs, // [uTLS]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
dicttls/hpke_aead_identifiers.go
Normal file
19
dicttls/hpke_aead_identifiers.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package dicttls
|
||||||
|
|
||||||
|
// source: https://www.iana.org/assignments/hpke/hpke.xhtml
|
||||||
|
// last updated: December 2023
|
||||||
|
|
||||||
|
const (
|
||||||
|
AEAD_AES_128_GCM uint16 = 0x0001 // NIST Special Publication 800-38D
|
||||||
|
AEAD_AES_256_GCM uint16 = 0x0002 // NIST Special Publication 800-38D
|
||||||
|
AEAD_CHACHA20_POLY1305 uint16 = 0x0003 // RFC 8439
|
||||||
|
AEAD_EXPORT_ONLY uint16 = 0xFFFF // RFC 9180
|
||||||
|
)
|
||||||
|
|
||||||
|
var DictAEADIdentifierValueIndexed = map[uint16]string{
|
||||||
|
0x0000: "Reserved", // RFC 9180
|
||||||
|
0x0001: "AES-128-GCM",
|
||||||
|
0x0002: "AES-256-GCM",
|
||||||
|
0x0003: "ChaCha20Poly1305",
|
||||||
|
0xFFFF: "Export-only", // RFC 9180
|
||||||
|
}
|
|
@ -1,19 +1,24 @@
|
||||||
package dicttls
|
package dicttls
|
||||||
|
|
||||||
// source: https://www.iana.org/assignments/tls-parameters/tls-kdf-ids.csv
|
// source: https://www.iana.org/assignments/hpke/hpke.xhtml
|
||||||
// last updated: March 2023
|
// last updated: December 2023
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HKDF_SHA256 uint16 = 0x0001
|
HKDF_SHA256 uint16 = 0x0001
|
||||||
HKDF_SHA384 uint16 = 0x0002
|
HKDF_SHA384 uint16 = 0x0002
|
||||||
|
HKDF_SHA512 uint16 = 0x0003
|
||||||
)
|
)
|
||||||
|
|
||||||
var DictKDFIdentifierValueIndexed = map[uint16]string{
|
var DictKDFIdentifierValueIndexed = map[uint16]string{
|
||||||
|
0x0000: "Reserved", // RFC 9180
|
||||||
0x0001: "HKDF_SHA256",
|
0x0001: "HKDF_SHA256",
|
||||||
0x0002: "HKDF_SHA384",
|
0x0002: "HKDF_SHA384",
|
||||||
|
0x0003: "HKDF_SHA512",
|
||||||
}
|
}
|
||||||
|
|
||||||
var DictKDFIdentifierNameIndexed = map[string]uint16{
|
var DictKDFIdentifierNameIndexed = map[string]uint16{
|
||||||
|
"Reserved": 0x0000, // RFC 9180
|
||||||
"HKDF_SHA256": 0x0001,
|
"HKDF_SHA256": 0x0001,
|
||||||
"HKDF_SHA384": 0x0002,
|
"HKDF_SHA384": 0x0002,
|
||||||
|
"HKDF_SHA512": 0x0003,
|
||||||
}
|
}
|
53
dicttls/hpke_kem_identifiers.go
Normal file
53
dicttls/hpke_kem_identifiers.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package dicttls
|
||||||
|
|
||||||
|
// source: https://www.iana.org/assignments/hpke/hpke.xhtml
|
||||||
|
// last updated: December 2023
|
||||||
|
|
||||||
|
const (
|
||||||
|
DHKEM_P256_HKDF_SHA256 uint16 = 0x0010 // RFC 5869
|
||||||
|
DHKEM_P384_HKDF_SHA384 uint16 = 0x0011 // RFC 5869
|
||||||
|
DHKEM_P521_HKDF_SHA512 uint16 = 0x0012 // RFC 5869
|
||||||
|
DHKEM_CP256_HKDF_SHA256 uint16 = 0x0013 // RFC 6090
|
||||||
|
DHKEM_CP384_HKDF_SHA384 uint16 = 0x0014 // RFC 6090
|
||||||
|
DHKEM_CP521_HKDF_SHA512 uint16 = 0x0015 // RFC 6090
|
||||||
|
DHKEM_SECP256K1_HKDF_SHA256 uint16 = 0x0016 // draft-wahby-cfrg-hpke-kem-secp256k1-01
|
||||||
|
|
||||||
|
DHKEM_X25519_HKDF_SHA256 uint16 = 0x0020 // RFC 7748
|
||||||
|
DHKEM_X448_HKDF_SHA512 uint16 = 0x0021 // RFC 7748
|
||||||
|
|
||||||
|
X25519_KYBER768_DRAFT00 uint16 = 0x0030 // draft-westerbaan-cfrg-hpke-xyber768d00-02
|
||||||
|
)
|
||||||
|
|
||||||
|
var DictKEMIdentifierValueIndexed = map[uint16]string{
|
||||||
|
0x0000: "Reserved", // RFC 9180
|
||||||
|
|
||||||
|
0x0010: "DHKEM(P-256, HKDF-SHA256)",
|
||||||
|
0x0011: "DHKEM(P-384, HKDF-SHA384)",
|
||||||
|
0x0012: "DHKEM(P-521, HKDF-SHA512)",
|
||||||
|
0x0013: "DHKEM(CP-256, HKDF-SHA256)",
|
||||||
|
0x0014: "DHKEM(CP-384, HKDF-SHA384)",
|
||||||
|
0x0015: "DHKEM(CP-521, HKDF-SHA512)",
|
||||||
|
0x0016: "DHKEM(secp256k1, HKDF-SHA256)",
|
||||||
|
|
||||||
|
0x0020: "DHKEM(X25519, HKDF-SHA256)",
|
||||||
|
0x0021: "DHKEM(X448, HKDF-SHA512)",
|
||||||
|
|
||||||
|
0x0030: "X25519Kyber768Draft00",
|
||||||
|
}
|
||||||
|
|
||||||
|
var DictKEMIdentifierNameIndexed = map[string]uint16{
|
||||||
|
"Reserved": 0x0000, // RFC 9180
|
||||||
|
|
||||||
|
"DHKEM(P-256, HKDF-SHA256)": 0x0010,
|
||||||
|
"DHKEM(P-384, HKDF-SHA384)": 0x0011,
|
||||||
|
"DHKEM(P-521, HKDF-SHA512)": 0x0012,
|
||||||
|
"DHKEM(CP-256, HKDF-SHA256)": 0x0013,
|
||||||
|
"DHKEM(CP-384, HKDF-SHA384)": 0x0014,
|
||||||
|
"DHKEM(CP-521, HKDF-SHA512)": 0x0015,
|
||||||
|
"DHKEM(secp256k1, HKDF-SHA256)": 0x0016,
|
||||||
|
|
||||||
|
"DHKEM(X25519, HKDF-SHA256)": 0x0020,
|
||||||
|
"DHKEM(X448, HKDF-SHA512)": 0x0021,
|
||||||
|
|
||||||
|
"X25519Kyber768Draft00": 0x0030,
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
package dicttls
|
|
||||||
|
|
||||||
// source: https://www.rfc-editor.org/rfc/rfc9180
|
|
||||||
// last updated: December 2023
|
|
||||||
|
|
||||||
const (
|
|
||||||
DHKEM_P256_HKDF_SHA256 uint16 = 0x0010 // RFC 5869
|
|
||||||
DHKEM_P384_HKDF_SHA384 uint16 = 0x0011 // RFC 5869
|
|
||||||
DHKEM_P521_HKDF_SHA512 uint16 = 0x0012 // RFC 5869
|
|
||||||
|
|
||||||
DHKEM_X25519_HKDF_SHA256 uint16 = 0x0020 // RFC 7748
|
|
||||||
DHKEM_X448_HKDF_SHA512 uint16 = 0x0021 // RFC 7748
|
|
||||||
)
|
|
||||||
|
|
||||||
var DictKEMIdentifierValueIndexed = map[uint16]string{
|
|
||||||
0x0000: "Reserved", // RFC 9180
|
|
||||||
|
|
||||||
0x0010: "DHKEM_P256_HKDF_SHA256",
|
|
||||||
0x0011: "DHKEM_P384_HKDF_SHA384",
|
|
||||||
0x0012: "DHKEM_P521_HKDF_SHA512",
|
|
||||||
|
|
||||||
0x0020: "DHKEM_X25519_HKDF_SHA256",
|
|
||||||
0x0021: "DHKEM_X448_HKDF_SHA512",
|
|
||||||
}
|
|
||||||
|
|
||||||
var DictKEMIdentifierNameIndexed = map[string]uint16{
|
|
||||||
"Reserved": 0x0000, // RFC 9180
|
|
||||||
|
|
||||||
"DHKEM_P256_HKDF_SHA256": 0x0010,
|
|
||||||
"DHKEM_P384_HKDF_SHA384": 0x0011,
|
|
||||||
"DHKEM_P521_HKDF_SHA512": 0x0012,
|
|
||||||
|
|
||||||
"DHKEM_X25519_HKDF_SHA256": 0x0020,
|
|
||||||
"DHKEM_X448_HKDF_SHA512": 0x0021,
|
|
||||||
}
|
|
127
examples/ech/main.go
Normal file
127
examples/ech/main.go
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
tls "github.com/refraction-networking/utls"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dialTimeout = time.Duration(15) * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// var requestHostname = "crypto.cloudflare.com" // speaks http2 and TLS 1.3 and ECH and PQ
|
||||||
|
// var requestAddr = "crypto.cloudflare.com:443"
|
||||||
|
// var requestPath = "/cdn-cgi/trace"
|
||||||
|
|
||||||
|
// var requestHostname = "tls-ech.dev" // speaks http2 and TLS 1.3 and ECH and PQ
|
||||||
|
// var requestAddr = "tls-ech.dev:443"
|
||||||
|
// var requestPath = "/"
|
||||||
|
|
||||||
|
var requestHostname = "defo.ie" // speaks http2 and TLS 1.3 and ECH and PQ
|
||||||
|
var requestAddr = "defo.ie:443"
|
||||||
|
var requestPath = "/ech-check.php"
|
||||||
|
|
||||||
|
// var requestHostname = "client.tlsfingerprint.io" // speaks http2 and TLS 1.3 and ECH and PQ
|
||||||
|
// var requestAddr = "client.tlsfingerprint.io:443"
|
||||||
|
// var requestPath = "/"
|
||||||
|
|
||||||
|
func HttpGetCustom(hostname string, addr string) (*http.Response, error) {
|
||||||
|
klw, err := os.OpenFile("./sslkeylogging.log", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("os.OpenFile error: %+v", err)
|
||||||
|
}
|
||||||
|
config := tls.Config{
|
||||||
|
ServerName: hostname,
|
||||||
|
KeyLogWriter: klw,
|
||||||
|
}
|
||||||
|
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
|
||||||
|
}
|
||||||
|
uTlsConn := tls.UClient(dialConn, &config, tls.HelloCustom)
|
||||||
|
defer uTlsConn.Close()
|
||||||
|
|
||||||
|
// do not use this particular spec in production
|
||||||
|
// make sure to generate a separate copy of ClientHelloSpec for every connection
|
||||||
|
spec, err := tls.UTLSIdToSpec(tls.HelloChrome_120)
|
||||||
|
// spec, err := tls.UTLSIdToSpec(tls.HelloFirefox_120)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("tls.UTLSIdToSpec error: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = uTlsConn.ApplyPreset(&spec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = uTlsConn.Handshake()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpGetOverConn(uTlsConn, uTlsConn.ConnectionState().NegotiatedProtocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpGetOverConn(conn net.Conn, alpn string) (*http.Response, error) {
|
||||||
|
req := &http.Request{
|
||||||
|
Method: "GET",
|
||||||
|
URL: &url.URL{Scheme: "https", Host: requestHostname, Path: requestPath},
|
||||||
|
Header: make(http.Header),
|
||||||
|
Host: requestHostname,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch alpn {
|
||||||
|
case "h2":
|
||||||
|
log.Println("HTTP/2 enabled")
|
||||||
|
req.Proto = "HTTP/2.0"
|
||||||
|
req.ProtoMajor = 2
|
||||||
|
req.ProtoMinor = 0
|
||||||
|
|
||||||
|
tr := http2.Transport{}
|
||||||
|
cConn, err := tr.NewClientConn(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cConn.RoundTrip(req)
|
||||||
|
case "http/1.1", "":
|
||||||
|
log.Println("Using HTTP/1.1")
|
||||||
|
req.Proto = "HTTP/1.1"
|
||||||
|
req.ProtoMajor = 1
|
||||||
|
req.ProtoMinor = 1
|
||||||
|
|
||||||
|
err := req.Write(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return http.ReadResponse(bufio.NewReader(conn), req)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported ALPN: %v", alpn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
resp, err := HttpGetCustom(requestHostname, requestAddr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Response: %+v\n", resp)
|
||||||
|
// read from resp.Body
|
||||||
|
body := make([]byte, 65535)
|
||||||
|
n, err := resp.Body.Read(body)
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Body: %s\n", body[:n])
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ func HttpGetByHelloID(hostname string, addr string, helloID tls.ClientHelloID) (
|
||||||
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
|
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
|
return httpGetOverConn(uTlsConn, uTlsConn.ConnectionState().NegotiatedProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this example generates a randomized fingeprint, then re-uses it in a follow-up connection
|
// this example generates a randomized fingeprint, then re-uses it in a follow-up connection
|
||||||
|
@ -80,7 +80,7 @@ func HttpGetConsistentRandomized(hostname string, addr string) (*http.Response,
|
||||||
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
|
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpGetOverConn(uTlsConn2, uTlsConn2.HandshakeState.ServerHello.AlpnProtocol)
|
return httpGetOverConn(uTlsConn2, uTlsConn2.ConnectionState().NegotiatedProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpGetExplicitRandom(hostname string, addr string) (*http.Response, error) {
|
func HttpGetExplicitRandom(hostname string, addr string) (*http.Response, error) {
|
||||||
|
@ -112,7 +112,7 @@ func HttpGetExplicitRandom(hostname string, addr string) (*http.Response, error)
|
||||||
fmt.Printf("#> ClientHello Random:\n%s", hex.Dump(uTlsConn.HandshakeState.Hello.Random))
|
fmt.Printf("#> ClientHello Random:\n%s", hex.Dump(uTlsConn.HandshakeState.Hello.Random))
|
||||||
fmt.Printf("#> ServerHello Random:\n%s", hex.Dump(uTlsConn.HandshakeState.ServerHello.Random))
|
fmt.Printf("#> ServerHello Random:\n%s", hex.Dump(uTlsConn.HandshakeState.ServerHello.Random))
|
||||||
|
|
||||||
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
|
return httpGetOverConn(uTlsConn, uTlsConn.ConnectionState().NegotiatedProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that the server will reject the fake ticket(unless you set up your server to accept them) and do full handshake
|
// Note that the server will reject the fake ticket(unless you set up your server to accept them) and do full handshake
|
||||||
|
@ -152,7 +152,7 @@ func HttpGetTicket(hostname string, addr string) (*http.Response, error) {
|
||||||
fmt.Println("#> This is how client hello with session ticket looked:")
|
fmt.Println("#> This is how client hello with session ticket looked:")
|
||||||
fmt.Print(hex.Dump(uTlsConn.HandshakeState.Hello.Raw))
|
fmt.Print(hex.Dump(uTlsConn.HandshakeState.Hello.Raw))
|
||||||
|
|
||||||
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
|
return httpGetOverConn(uTlsConn, uTlsConn.ConnectionState().NegotiatedProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that the server will reject the fake ticket(unless you set up your server to accept them) and do full handshake
|
// Note that the server will reject the fake ticket(unless you set up your server to accept them) and do full handshake
|
||||||
|
@ -183,7 +183,7 @@ func HttpGetTicketHelloID(hostname string, addr string, helloID tls.ClientHelloI
|
||||||
fmt.Println("#> This is how client hello with session ticket looked:")
|
fmt.Println("#> This is how client hello with session ticket looked:")
|
||||||
fmt.Print(hex.Dump(uTlsConn.HandshakeState.Hello.Raw))
|
fmt.Print(hex.Dump(uTlsConn.HandshakeState.Hello.Raw))
|
||||||
|
|
||||||
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
|
return httpGetOverConn(uTlsConn, uTlsConn.ConnectionState().NegotiatedProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpGetCustom(hostname string, addr string) (*http.Response, error) {
|
func HttpGetCustom(hostname string, addr string) (*http.Response, error) {
|
||||||
|
@ -253,7 +253,7 @@ func HttpGetCustom(hostname string, addr string) (*http.Response, error) {
|
||||||
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
|
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
|
return httpGetOverConn(uTlsConn, uTlsConn.ConnectionState().NegotiatedProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
var roller *tls.Roller
|
var roller *tls.Roller
|
||||||
|
@ -277,7 +277,7 @@ func HttpGetGoogleWithRoller() (*http.Response, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpGetOverConn(c, c.HandshakeState.ServerHello.AlpnProtocol)
|
return httpGetOverConn(c, c.ConnectionState().NegotiatedProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func forgeConn() {
|
func forgeConn() {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -9,7 +9,7 @@ retract (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.5
|
github.com/andybalholm/brotli v1.0.5
|
||||||
github.com/cloudflare/circl v1.3.3
|
github.com/cloudflare/circl v1.3.6
|
||||||
github.com/klauspost/compress v1.16.7
|
github.com/klauspost/compress v1.16.7
|
||||||
github.com/quic-go/quic-go v0.37.4
|
github.com/quic-go/quic-go v0.37.4
|
||||||
golang.org/x/crypto v0.14.0
|
golang.org/x/crypto v0.14.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1,7 +1,7 @@
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
|
|
@ -874,6 +874,8 @@ func TestCloneNonFuncFields(t *testing.T) {
|
||||||
continue // these are unexported fields that are handled separately
|
continue // these are unexported fields that are handled separately
|
||||||
case "ApplicationSettings": // [UTLS] ALPS (Application Settings)
|
case "ApplicationSettings": // [UTLS] ALPS (Application Settings)
|
||||||
f.Set(reflect.ValueOf(map[string][]byte{"a": {1}}))
|
f.Set(reflect.ValueOf(map[string][]byte{"a": {1}}))
|
||||||
|
case "ECHConfigs": // [UTLS] ECH (Encrypted Client Hello) Configs
|
||||||
|
f.Set(reflect.ValueOf([]ECHConfig{{Version: 1}}))
|
||||||
default:
|
default:
|
||||||
t.Errorf("all fields must be accounted for, but saw unknown field %q", fn)
|
t.Errorf("all fields must be accounted for, but saw unknown field %q", fn)
|
||||||
}
|
}
|
||||||
|
|
12
u_common.go
12
u_common.go
|
@ -35,9 +35,11 @@ const (
|
||||||
extensionNextProtoNeg uint16 = 13172 // not IANA assigned. Removed by crypto/tls since Nov 2019
|
extensionNextProtoNeg uint16 = 13172 // not IANA assigned. Removed by crypto/tls since Nov 2019
|
||||||
|
|
||||||
utlsExtensionPadding uint16 = 21
|
utlsExtensionPadding uint16 = 21
|
||||||
utlsExtensionCompressCertificate uint16 = 27 // https://datatracker.ietf.org/doc/html/rfc8879#section-7.1
|
utlsExtensionCompressCertificate uint16 = 27 // https://datatracker.ietf.org/doc/html/rfc8879#section-7.1
|
||||||
utlsExtensionApplicationSettings uint16 = 17513 // not IANA assigned
|
utlsExtensionApplicationSettings uint16 = 17513 // not IANA assigned
|
||||||
utlsFakeExtensionCustom uint16 = 1234 // not IANA assigned, for ALPS
|
utlsFakeExtensionCustom uint16 = 1234 // not IANA assigned, for ALPS
|
||||||
|
utlsExtensionECH uint16 = 0xfe0d // draft-ietf-tls-esni-17
|
||||||
|
utlsExtensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-17
|
||||||
|
|
||||||
// extensions with 'fake' prefix break connection, if server echoes them back
|
// extensions with 'fake' prefix break connection, if server echoes them back
|
||||||
fakeExtensionEncryptThenMAC uint16 = 22
|
fakeExtensionEncryptThenMAC uint16 = 22
|
||||||
|
@ -593,6 +595,7 @@ var (
|
||||||
HelloFirefox_99 = ClientHelloID{helloFirefox, "99", nil, nil}
|
HelloFirefox_99 = ClientHelloID{helloFirefox, "99", nil, nil}
|
||||||
HelloFirefox_102 = ClientHelloID{helloFirefox, "102", nil, nil}
|
HelloFirefox_102 = ClientHelloID{helloFirefox, "102", nil, nil}
|
||||||
HelloFirefox_105 = ClientHelloID{helloFirefox, "105", nil, nil}
|
HelloFirefox_105 = ClientHelloID{helloFirefox, "105", nil, nil}
|
||||||
|
HelloFirefox_120 = ClientHelloID{helloFirefox, "120", nil, nil}
|
||||||
|
|
||||||
HelloChrome_Auto = HelloChrome_106_Shuffle
|
HelloChrome_Auto = HelloChrome_106_Shuffle
|
||||||
HelloChrome_58 = ClientHelloID{helloChrome, "58", nil, nil}
|
HelloChrome_58 = ClientHelloID{helloChrome, "58", nil, nil}
|
||||||
|
@ -618,6 +621,9 @@ var (
|
||||||
HelloChrome_115_PQ = ClientHelloID{helloChrome, "115_PQ", nil, nil}
|
HelloChrome_115_PQ = ClientHelloID{helloChrome, "115_PQ", nil, nil}
|
||||||
HelloChrome_115_PQ_PSK = ClientHelloID{helloChrome, "115_PQ_PSK", nil, nil}
|
HelloChrome_115_PQ_PSK = ClientHelloID{helloChrome, "115_PQ_PSK", nil, nil}
|
||||||
|
|
||||||
|
// Chrome w/ Post-Quantum Key Agreement and Encrypted ClientHello
|
||||||
|
HelloChrome_120 = ClientHelloID{helloChrome, "120", nil, nil}
|
||||||
|
|
||||||
HelloIOS_Auto = HelloIOS_14
|
HelloIOS_Auto = HelloIOS_14
|
||||||
HelloIOS_11_1 = ClientHelloID{helloIOS, "111", nil, nil} // legacy "111" means 11.1
|
HelloIOS_11_1 = ClientHelloID{helloIOS, "111", nil, nil} // legacy "111" means 11.1
|
||||||
HelloIOS_12_1 = ClientHelloID{helloIOS, "12.1", nil, nil}
|
HelloIOS_12_1 = ClientHelloID{helloIOS, "12.1", nil, nil}
|
||||||
|
|
15
u_conn.go
15
u_conn.go
|
@ -48,6 +48,9 @@ type UConn struct {
|
||||||
// algorithms, as specified in the ClientHello. This is only relevant client-side, for the
|
// algorithms, as specified in the ClientHello. This is only relevant client-side, for the
|
||||||
// server certificate. All other forms of certificate compression are unsupported.
|
// server certificate. All other forms of certificate compression are unsupported.
|
||||||
certCompressionAlgs []CertCompressionAlgo
|
certCompressionAlgs []CertCompressionAlgo
|
||||||
|
|
||||||
|
// ech extension is a shortcut to the ECH extension in the Extensions slice if there is one.
|
||||||
|
ech ECHExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
// UClient returns a new uTLS client, with behavior depending on clientHelloID.
|
// UClient returns a new uTLS client, with behavior depending on clientHelloID.
|
||||||
|
@ -616,13 +619,19 @@ func (uconn *UConn) ApplyConfig() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uconn *UConn) MarshalClientHello() error {
|
func (uconn *UConn) MarshalClientHello() error {
|
||||||
|
if uconn.ech != nil {
|
||||||
|
if err := uconn.ech.Configure(uconn.config.ECHConfigs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return uconn.ech.MarshalClientHello(uconn)
|
||||||
|
}
|
||||||
hello := uconn.HandshakeState.Hello
|
hello := uconn.HandshakeState.Hello
|
||||||
headerLength := 2 + 32 + 1 + len(hello.SessionId) +
|
headerLength := 2 + 32 + 1 + len(hello.SessionId) +
|
||||||
2 + len(hello.CipherSuites)*2 +
|
2 + len(hello.CipherSuites)*2 +
|
||||||
1 + len(hello.CompressionMethods)
|
1 + len(hello.CompressionMethods)
|
||||||
|
|
||||||
extensionsLen := 0
|
extensionsLen := 0
|
||||||
var paddingExt *UtlsPaddingExtension
|
var paddingExt *UtlsPaddingExtension // reference to padding extension, if present
|
||||||
for _, ext := range uconn.Extensions {
|
for _, ext := range uconn.Extensions {
|
||||||
if pe, ok := ext.(*UtlsPaddingExtension); !ok {
|
if pe, ok := ext.(*UtlsPaddingExtension); !ok {
|
||||||
// If not padding - just add length of extension to total length
|
// If not padding - just add length of extension to total length
|
||||||
|
@ -859,6 +868,7 @@ func (c *Conn) utlsHandshakeMessageType(msgType byte) (handshakeMessage, error)
|
||||||
// Extending (*Conn).connectionStateLocked()
|
// Extending (*Conn).connectionStateLocked()
|
||||||
func (c *Conn) utlsConnectionStateLocked(state *ConnectionState) {
|
func (c *Conn) utlsConnectionStateLocked(state *ConnectionState) {
|
||||||
state.PeerApplicationSettings = c.utls.peerApplicationSettings
|
state.PeerApplicationSettings = c.utls.peerApplicationSettings
|
||||||
|
state.ECHRetryConfigs = c.utls.echRetryConfigs
|
||||||
}
|
}
|
||||||
|
|
||||||
type utlsConnExtraFields struct {
|
type utlsConnExtraFields struct {
|
||||||
|
@ -867,5 +877,8 @@ type utlsConnExtraFields struct {
|
||||||
peerApplicationSettings []byte
|
peerApplicationSettings []byte
|
||||||
localApplicationSettings []byte
|
localApplicationSettings []byte
|
||||||
|
|
||||||
|
// Encrypted Client Hello (ECH)
|
||||||
|
echRetryConfigs []ECHConfig
|
||||||
|
|
||||||
sessionController *sessionController
|
sessionController *sessionController
|
||||||
}
|
}
|
||||||
|
|
236
u_ech.go
Normal file
236
u_ech.go
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unstable API: This is a work in progress and may change in the future. Using
|
||||||
|
// it in your application may cause your application to break when updating to
|
||||||
|
// a new version of uTLS.
|
||||||
|
|
||||||
|
const (
|
||||||
|
OuterClientHello byte = 0x00
|
||||||
|
InnerClientHello byte = 0x01
|
||||||
|
)
|
||||||
|
|
||||||
|
type EncryptedClientHelloExtension interface {
|
||||||
|
// TLSExtension must be implemented by all EncryptedClientHelloExtension implementations.
|
||||||
|
TLSExtension
|
||||||
|
|
||||||
|
// Configure configures the EncryptedClientHelloExtension with the given slice of ECHConfig.
|
||||||
|
Configure([]ECHConfig) error
|
||||||
|
|
||||||
|
// MarshalClientHello is called by (*UConn).MarshalClientHello() when an ECH extension
|
||||||
|
// is present to allow the ECH extension to take control of the generation of the
|
||||||
|
// entire ClientHello message.
|
||||||
|
MarshalClientHello(*UConn) error
|
||||||
|
|
||||||
|
mustEmbedUnimplementedECHExtension()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ECHExtension = EncryptedClientHelloExtension // alias
|
||||||
|
|
||||||
|
// type guard: GREASEEncryptedClientHelloExtension must implement EncryptedClientHelloExtension
|
||||||
|
var (
|
||||||
|
_ EncryptedClientHelloExtension = (*GREASEEncryptedClientHelloExtension)(nil)
|
||||||
|
|
||||||
|
_ EncryptedClientHelloExtension = (*UnimplementedECHExtension)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type GREASEEncryptedClientHelloExtension struct {
|
||||||
|
CandidateCipherSuites []HPKESymmetricCipherSuite
|
||||||
|
cipherSuite HPKESymmetricCipherSuite // randomly picked from CandidateCipherSuites or generated if empty
|
||||||
|
CandidateConfigIds []uint8
|
||||||
|
configId uint8 // randomly picked from CandidateConfigIds or generated if empty
|
||||||
|
EncapsulatedKey []byte // if empty, will generate random bytes
|
||||||
|
CandidatePayloadLens []uint16 // Pre-encryption. If 0, will pick 128(+16=144)
|
||||||
|
payload []byte // payload should be calculated ONCE and stored here, HRR will reuse this
|
||||||
|
|
||||||
|
initOnce sync.Once
|
||||||
|
|
||||||
|
UnimplementedECHExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
type GREASEECHExtension = GREASEEncryptedClientHelloExtension // alias
|
||||||
|
|
||||||
|
// init initializes the GREASEEncryptedClientHelloExtension with random values if they are not set.
|
||||||
|
//
|
||||||
|
// Based on cloudflare/go's echGenerateGreaseExt()
|
||||||
|
func (g *GREASEEncryptedClientHelloExtension) init() error {
|
||||||
|
var initErr error
|
||||||
|
g.initOnce.Do(func() {
|
||||||
|
// Set the config_id field to a random byte.
|
||||||
|
//
|
||||||
|
// Note: must not reuse this extension unless for HRR. It is required
|
||||||
|
// to generate new random bytes for config_id for each new ClientHello,
|
||||||
|
// but reuse the same config_id for HRR.
|
||||||
|
if len(g.CandidateConfigIds) == 0 {
|
||||||
|
var b []byte = make([]byte, 1)
|
||||||
|
_, err := rand.Read(b[:])
|
||||||
|
if err != nil {
|
||||||
|
initErr = fmt.Errorf("error generating random byte for config_id: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.configId = b[0]
|
||||||
|
} else {
|
||||||
|
// randomly pick one from the list
|
||||||
|
rndIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(g.CandidateConfigIds))))
|
||||||
|
if err != nil {
|
||||||
|
initErr = fmt.Errorf("error generating random index for config_id: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.configId = g.CandidateConfigIds[rndIndex.Int64()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cipher_suite field to a supported HpkeSymmetricCipherSuite.
|
||||||
|
// The selection SHOULD vary to exercise all supported configurations,
|
||||||
|
// but MAY be held constant for successive connections to the same server
|
||||||
|
// in the same session.
|
||||||
|
if len(g.CandidateCipherSuites) == 0 {
|
||||||
|
_, kdf, aead := defaultHPKESuite.Params()
|
||||||
|
g.cipherSuite = HPKESymmetricCipherSuite{uint16(kdf), uint16(aead)}
|
||||||
|
} else {
|
||||||
|
// randomly pick one from the list
|
||||||
|
rndIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(g.CandidateCipherSuites))))
|
||||||
|
if err != nil {
|
||||||
|
initErr = fmt.Errorf("error generating random index for cipher_suite: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.cipherSuite = HPKESymmetricCipherSuite{
|
||||||
|
g.CandidateCipherSuites[rndIndex.Int64()].KdfId,
|
||||||
|
g.CandidateCipherSuites[rndIndex.Int64()].AeadId,
|
||||||
|
}
|
||||||
|
// aead = hpke.AEAD(g.cipherSuite.AeadId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.EncapsulatedKey) == 0 {
|
||||||
|
// use default random key from cloudflare/go
|
||||||
|
kem := hpke.KEM_X25519_HKDF_SHA256
|
||||||
|
|
||||||
|
pk, err := kem.Scheme().UnmarshalBinaryPublicKey(dummyX25519PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
initErr = fmt.Errorf("tls: grease ech: failed to parse dummy public key: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sender, err := defaultHPKESuite.NewSender(pk, nil)
|
||||||
|
if err != nil {
|
||||||
|
initErr = fmt.Errorf("tls: grease ech: failed to create sender: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
g.EncapsulatedKey, _, err = sender.Setup(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
initErr = fmt.Errorf("tls: grease ech: failed to setup encapsulated key: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.payload) == 0 {
|
||||||
|
if len(g.CandidatePayloadLens) == 0 {
|
||||||
|
g.CandidatePayloadLens = []uint16{128}
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomly pick one from the list
|
||||||
|
rndIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(g.CandidatePayloadLens))))
|
||||||
|
if err != nil {
|
||||||
|
initErr = fmt.Errorf("error generating random index for payload length: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
initErr = g.randomizePayload(g.CandidatePayloadLens[rndIndex.Int64()])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return initErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GREASEEncryptedClientHelloExtension) randomizePayload(encodedHelloInnerLen uint16) error {
|
||||||
|
if len(g.payload) != 0 {
|
||||||
|
return errors.New("tls: grease ech: regenerating payload is forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
aead := hpke.AEAD(g.cipherSuite.AeadId)
|
||||||
|
g.payload = make([]byte, int(aead.CipherLen(uint(encodedHelloInnerLen))))
|
||||||
|
_, err := rand.Read(g.payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("tls: generating grease ech payload: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For ECH extensions, writeToUConn simply points the ech field in UConn to the extension.
|
||||||
|
func (g *GREASEEncryptedClientHelloExtension) writeToUConn(uconn *UConn) error {
|
||||||
|
// uconn.ech = g // don't do this, so we don't intercept the MarshalClientHello() call
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GREASEEncryptedClientHelloExtension) Len() int {
|
||||||
|
g.init()
|
||||||
|
return 2 + 2 + 1 /* ClientHello Type */ + 4 /* CipherSuite */ + 1 /* Config ID */ + 2 + len(g.EncapsulatedKey) + 2 + len(g.payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GREASEEncryptedClientHelloExtension) Read(b []byte) (int, error) {
|
||||||
|
if len(b) < g.Len() {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = byte(utlsExtensionECH >> 8)
|
||||||
|
b[1] = byte(utlsExtensionECH & 0xFF)
|
||||||
|
b[2] = byte((g.Len() - 4) >> 8)
|
||||||
|
b[3] = byte((g.Len() - 4) & 0xFF)
|
||||||
|
b[4] = OuterClientHello
|
||||||
|
b[5] = byte(g.cipherSuite.KdfId >> 8)
|
||||||
|
b[6] = byte(g.cipherSuite.KdfId & 0xFF)
|
||||||
|
b[7] = byte(g.cipherSuite.AeadId >> 8)
|
||||||
|
b[8] = byte(g.cipherSuite.AeadId & 0xFF)
|
||||||
|
b[9] = g.configId
|
||||||
|
b[10] = byte(len(g.EncapsulatedKey) >> 8)
|
||||||
|
b[11] = byte(len(g.EncapsulatedKey) & 0xFF)
|
||||||
|
copy(b[12:], g.EncapsulatedKey)
|
||||||
|
b[12+len(g.EncapsulatedKey)] = byte(len(g.payload) >> 8)
|
||||||
|
b[12+len(g.EncapsulatedKey)+1] = byte(len(g.payload) & 0xFF)
|
||||||
|
copy(b[12+len(g.EncapsulatedKey)+2:], g.payload)
|
||||||
|
|
||||||
|
return g.Len(), io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GREASEEncryptedClientHelloExtension) Configure([]ECHConfig) error {
|
||||||
|
return errors.New("tls: grease ech: Configure() is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GREASEEncryptedClientHelloExtension) MarshalClientHello(*UConn) error {
|
||||||
|
return errors.New("tls: grease ech: MarshalClientHello() is not implemented, use (*UConn).MarshalClientHello() instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnimplementedECHExtension struct{}
|
||||||
|
|
||||||
|
func (*UnimplementedECHExtension) writeToUConn(_ *UConn) error {
|
||||||
|
return errors.New("tls: unimplemented ECHExtension")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UnimplementedECHExtension) Len() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UnimplementedECHExtension) Read(_ []byte) (int, error) {
|
||||||
|
return 0, errors.New("tls: unimplemented ECHExtension")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UnimplementedECHExtension) Configure([]ECHConfig) error {
|
||||||
|
return errors.New("tls: unimplemented ECHExtension")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UnimplementedECHExtension) MarshalClientHello(*UConn) error {
|
||||||
|
return errors.New("tls: unimplemented ECHExtension")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*UnimplementedECHExtension) mustEmbedUnimplementedECHExtension() {
|
||||||
|
panic("mustEmbedUnimplementedECHExtension() is not implemented")
|
||||||
|
}
|
135
u_ech_config.go
Normal file
135
u_ech_config.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ECHConfigContents struct {
|
||||||
|
KeyConfig HPKEKeyConfig
|
||||||
|
MaximumNameLength uint8
|
||||||
|
PublicName []byte
|
||||||
|
// Extensions []TLSExtension // ignored for now
|
||||||
|
rawExtensions []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalECHConfigContents(contents []byte) (ECHConfigContents, error) {
|
||||||
|
var (
|
||||||
|
contentCryptobyte = cryptobyte.String(contents)
|
||||||
|
config ECHConfigContents
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse KeyConfig
|
||||||
|
var t cryptobyte.String
|
||||||
|
if !contentCryptobyte.ReadUint8(&config.KeyConfig.ConfigId) ||
|
||||||
|
!contentCryptobyte.ReadUint16(&config.KeyConfig.KemId) ||
|
||||||
|
!contentCryptobyte.ReadUint16LengthPrefixed(&t) ||
|
||||||
|
!t.ReadBytes(&config.KeyConfig.rawPublicKey, len(t)) ||
|
||||||
|
!contentCryptobyte.ReadUint16LengthPrefixed(&t) ||
|
||||||
|
len(t)%4 != 0 {
|
||||||
|
return config, errors.New("error parsing KeyConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse all CipherSuites in KeyConfig
|
||||||
|
config.KeyConfig.CipherSuites = nil
|
||||||
|
for !t.Empty() {
|
||||||
|
var kdfId, aeadId uint16
|
||||||
|
if !t.ReadUint16(&kdfId) || !t.ReadUint16(&aeadId) {
|
||||||
|
// This indicates an internal bug.
|
||||||
|
panic("internal error while parsing contents.cipher_suites")
|
||||||
|
}
|
||||||
|
config.KeyConfig.CipherSuites = append(config.KeyConfig.CipherSuites, HPKESymmetricCipherSuite{kdfId, aeadId})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !contentCryptobyte.ReadUint8(&config.MaximumNameLength) ||
|
||||||
|
!contentCryptobyte.ReadUint8LengthPrefixed(&t) ||
|
||||||
|
!t.ReadBytes(&config.PublicName, len(t)) ||
|
||||||
|
!contentCryptobyte.ReadUint16LengthPrefixed(&t) ||
|
||||||
|
!t.ReadBytes(&config.rawExtensions, len(t)) ||
|
||||||
|
!contentCryptobyte.Empty() {
|
||||||
|
return config, errors.New("error parsing ECHConfigContents")
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (echcc *ECHConfigContents) ParsePublicKey() error {
|
||||||
|
var err error
|
||||||
|
kem := hpke.KEM(echcc.KeyConfig.KemId)
|
||||||
|
if !kem.IsValid() {
|
||||||
|
return errors.New("invalid KEM")
|
||||||
|
}
|
||||||
|
echcc.KeyConfig.PublicKey, err = kem.Scheme().UnmarshalBinaryPublicKey(echcc.KeyConfig.rawPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing public key: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ECHConfig struct {
|
||||||
|
Version uint16
|
||||||
|
Length uint16
|
||||||
|
Contents ECHConfigContents
|
||||||
|
|
||||||
|
raw []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalECHConfigs parses a sequence of ECH configurations.
|
||||||
|
//
|
||||||
|
// Ported from cloudflare/go
|
||||||
|
func UnmarshalECHConfigs(raw []byte) ([]ECHConfig, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
config ECHConfig
|
||||||
|
t, contents cryptobyte.String
|
||||||
|
)
|
||||||
|
configs := make([]ECHConfig, 0)
|
||||||
|
s := cryptobyte.String(raw)
|
||||||
|
if !s.ReadUint16LengthPrefixed(&t) || !s.Empty() {
|
||||||
|
return configs, errors.New("error parsing configs")
|
||||||
|
}
|
||||||
|
raw = raw[2:]
|
||||||
|
ConfigsLoop:
|
||||||
|
for !t.Empty() {
|
||||||
|
l := len(t)
|
||||||
|
if !t.ReadUint16(&config.Version) ||
|
||||||
|
!t.ReadUint16LengthPrefixed(&contents) {
|
||||||
|
return nil, errors.New("error parsing config")
|
||||||
|
}
|
||||||
|
config.Length = uint16(len(contents))
|
||||||
|
n := l - len(t)
|
||||||
|
config.raw = raw[:n]
|
||||||
|
raw = raw[n:]
|
||||||
|
|
||||||
|
if config.Version != utlsExtensionECH {
|
||||||
|
continue ConfigsLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** cloudflare/go original ****/
|
||||||
|
// if !readConfigContents(&contents, &config) {
|
||||||
|
// return nil, errors.New("error parsing config contents")
|
||||||
|
// }
|
||||||
|
|
||||||
|
config.Contents, err = UnmarshalECHConfigContents(contents)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing config contents: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** cloudflare/go original ****/
|
||||||
|
// kem := hpke.KEM(config.kemId)
|
||||||
|
// if !kem.IsValid() {
|
||||||
|
// continue ConfigsLoop
|
||||||
|
// }
|
||||||
|
// config.pk, err = kem.Scheme().UnmarshalBinaryPublicKey(config.rawPublicKey)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("error parsing public key: %s", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
config.Contents.ParsePublicKey() // parse the bytes into a public key
|
||||||
|
|
||||||
|
configs = append(configs, config)
|
||||||
|
}
|
||||||
|
return configs, nil
|
||||||
|
}
|
|
@ -141,6 +141,7 @@ func (hs *clientHandshakeStateTLS13) sendClientEncryptedExtensions() error {
|
||||||
func (hs *clientHandshakeStateTLS13) utlsReadServerParameters(encryptedExtensions *encryptedExtensionsMsg) error {
|
func (hs *clientHandshakeStateTLS13) utlsReadServerParameters(encryptedExtensions *encryptedExtensionsMsg) error {
|
||||||
hs.c.utls.hasApplicationSettings = encryptedExtensions.utls.hasApplicationSettings
|
hs.c.utls.hasApplicationSettings = encryptedExtensions.utls.hasApplicationSettings
|
||||||
hs.c.utls.peerApplicationSettings = encryptedExtensions.utls.applicationSettings
|
hs.c.utls.peerApplicationSettings = encryptedExtensions.utls.applicationSettings
|
||||||
|
hs.c.utls.echRetryConfigs = encryptedExtensions.utls.echRetryConfigs
|
||||||
|
|
||||||
if hs.c.utls.hasApplicationSettings {
|
if hs.c.utls.hasApplicationSettings {
|
||||||
if hs.uconn.vers < VersionTLS13 {
|
if hs.uconn.vers < VersionTLS13 {
|
||||||
|
@ -160,6 +161,23 @@ func (hs *clientHandshakeStateTLS13) utlsReadServerParameters(encryptedExtension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(hs.c.utls.echRetryConfigs) > 0 {
|
||||||
|
if hs.uconn.vers < VersionTLS13 {
|
||||||
|
return errors.New("tls: server sent ECH retry configs at invalid version")
|
||||||
|
}
|
||||||
|
|
||||||
|
// find ECH extension in ClientHello
|
||||||
|
var echIncluded bool
|
||||||
|
for _, ext := range hs.uconn.Extensions {
|
||||||
|
if _, ok := ext.(ECHExtension); ok {
|
||||||
|
echIncluded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !echIncluded {
|
||||||
|
return errors.New("tls: server sent ECH retry configs without client sending ECH extension")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ func (m *utlsCompressedCertificateMsg) unmarshal(data []byte) bool {
|
||||||
type utlsEncryptedExtensionsMsgExtraFields struct {
|
type utlsEncryptedExtensionsMsgExtraFields struct {
|
||||||
hasApplicationSettings bool
|
hasApplicationSettings bool
|
||||||
applicationSettings []byte
|
applicationSettings []byte
|
||||||
|
echRetryConfigs []ECHConfig
|
||||||
customExtension []byte
|
customExtension []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +65,12 @@ func (m *encryptedExtensionsMsg) utlsUnmarshal(extension uint16, extData cryptob
|
||||||
case utlsExtensionApplicationSettings:
|
case utlsExtensionApplicationSettings:
|
||||||
m.utls.hasApplicationSettings = true
|
m.utls.hasApplicationSettings = true
|
||||||
m.utls.applicationSettings = []byte(extData)
|
m.utls.applicationSettings = []byte(extData)
|
||||||
|
case utlsExtensionECH:
|
||||||
|
var err error
|
||||||
|
m.utls.echRetryConfigs, err = UnmarshalECHConfigs([]byte(extData))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true // success/unknown extension
|
return true // success/unknown extension
|
||||||
}
|
}
|
||||||
|
|
62
u_hpke.go
Normal file
62
u_hpke.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
"github.com/cloudflare/circl/kem"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HPKERawPublicKey = []byte
|
||||||
|
type HPKE_KEM_ID = uint16 // RFC 9180
|
||||||
|
type HPKE_KDF_ID = uint16 // RFC 9180
|
||||||
|
type HPKE_AEAD_ID = uint16 // RFC 9180
|
||||||
|
|
||||||
|
type HPKESymmetricCipherSuite struct {
|
||||||
|
KdfId HPKE_KDF_ID
|
||||||
|
AeadId HPKE_AEAD_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
type HPKEKeyConfig struct {
|
||||||
|
ConfigId uint8
|
||||||
|
KemId HPKE_KEM_ID
|
||||||
|
PublicKey kem.PublicKey
|
||||||
|
rawPublicKey HPKERawPublicKey
|
||||||
|
CipherSuites []HPKESymmetricCipherSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultHPKESuite hpke.Suite
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
defaultHPKESuite, err = hpkeAssembleSuite(
|
||||||
|
uint16(hpke.KEM_X25519_HKDF_SHA256),
|
||||||
|
uint16(hpke.KDF_HKDF_SHA256),
|
||||||
|
uint16(hpke.AEAD_AES128GCM),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("hpke: mandatory-to-implement cipher suite not supported: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hpkeAssembleSuite(kemId, kdfId, aeadId uint16) (hpke.Suite, error) {
|
||||||
|
kem := hpke.KEM(kemId)
|
||||||
|
if !kem.IsValid() {
|
||||||
|
return hpke.Suite{}, errors.New("KEM is not supported")
|
||||||
|
}
|
||||||
|
kdf := hpke.KDF(kdfId)
|
||||||
|
if !kdf.IsValid() {
|
||||||
|
return hpke.Suite{}, errors.New("KDF is not supported")
|
||||||
|
}
|
||||||
|
aead := hpke.AEAD(aeadId)
|
||||||
|
if !aead.IsValid() {
|
||||||
|
return hpke.Suite{}, errors.New("AEAD is not supported")
|
||||||
|
}
|
||||||
|
return hpke.NewSuite(kem, kdf, aead), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var dummyX25519PublicKey = []byte{
|
||||||
|
143, 38, 37, 36, 12, 6, 229, 30, 140, 27, 167, 73, 26, 100, 203, 107, 216,
|
||||||
|
81, 163, 222, 52, 211, 54, 210, 46, 37, 78, 216, 157, 97, 241, 244,
|
||||||
|
}
|
207
u_parrots.go
207
u_parrots.go
|
@ -14,6 +14,8 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/refraction-networking/utls/dicttls"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrUnknownClientHelloID = errors.New("tls: unknown ClientHelloID")
|
var ErrUnknownClientHelloID = errors.New("tls: unknown ClientHelloID")
|
||||||
|
@ -656,6 +658,96 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
|
||||||
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
|
&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle},
|
||||||
}),
|
}),
|
||||||
}, nil
|
}, nil
|
||||||
|
// Chrome w/ Post-Quantum Key Agreement and ECH
|
||||||
|
case HelloChrome_120:
|
||||||
|
return ClientHelloSpec{
|
||||||
|
CipherSuites: []uint16{
|
||||||
|
GREASE_PLACEHOLDER,
|
||||||
|
TLS_AES_128_GCM_SHA256,
|
||||||
|
TLS_AES_256_GCM_SHA384,
|
||||||
|
TLS_CHACHA20_POLY1305_SHA256,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
|
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
},
|
||||||
|
CompressionMethods: []byte{
|
||||||
|
0x00, // compressionNone
|
||||||
|
},
|
||||||
|
Extensions: ShuffleChromeTLSExtensions([]TLSExtension{
|
||||||
|
&UtlsGREASEExtension{},
|
||||||
|
&SNIExtension{},
|
||||||
|
&ExtendedMasterSecretExtension{},
|
||||||
|
&RenegotiationInfoExtension{Renegotiation: RenegotiateOnceAsClient},
|
||||||
|
&SupportedCurvesExtension{[]CurveID{
|
||||||
|
GREASE_PLACEHOLDER,
|
||||||
|
X25519Kyber768Draft00,
|
||||||
|
X25519,
|
||||||
|
CurveP256,
|
||||||
|
CurveP384,
|
||||||
|
}},
|
||||||
|
&SupportedPointsExtension{SupportedPoints: []byte{
|
||||||
|
0x00, // pointFormatUncompressed
|
||||||
|
}},
|
||||||
|
&SessionTicketExtension{},
|
||||||
|
&ALPNExtension{AlpnProtocols: []string{"h2", "http/1.1"}},
|
||||||
|
&StatusRequestExtension{},
|
||||||
|
&SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []SignatureScheme{
|
||||||
|
ECDSAWithP256AndSHA256,
|
||||||
|
PSSWithSHA256,
|
||||||
|
PKCS1WithSHA256,
|
||||||
|
ECDSAWithP384AndSHA384,
|
||||||
|
PSSWithSHA384,
|
||||||
|
PKCS1WithSHA384,
|
||||||
|
PSSWithSHA512,
|
||||||
|
PKCS1WithSHA512,
|
||||||
|
}},
|
||||||
|
&SCTExtension{},
|
||||||
|
&KeyShareExtension{[]KeyShare{
|
||||||
|
{Group: CurveID(GREASE_PLACEHOLDER), Data: []byte{0}},
|
||||||
|
{Group: X25519Kyber768Draft00},
|
||||||
|
{Group: X25519},
|
||||||
|
}},
|
||||||
|
&PSKKeyExchangeModesExtension{[]uint8{
|
||||||
|
PskModeDHE,
|
||||||
|
}},
|
||||||
|
&SupportedVersionsExtension{[]uint16{
|
||||||
|
GREASE_PLACEHOLDER,
|
||||||
|
VersionTLS13,
|
||||||
|
VersionTLS12,
|
||||||
|
}},
|
||||||
|
&UtlsCompressCertExtension{[]CertCompressionAlgo{
|
||||||
|
CertCompressionBrotli,
|
||||||
|
}},
|
||||||
|
&ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}},
|
||||||
|
&GREASEEncryptedClientHelloExtension{
|
||||||
|
CandidateCipherSuites: []HPKESymmetricCipherSuite{
|
||||||
|
{
|
||||||
|
KdfId: dicttls.HKDF_SHA256,
|
||||||
|
AeadId: dicttls.AEAD_AES_128_GCM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KdfId: dicttls.HKDF_SHA256,
|
||||||
|
AeadId: dicttls.AEAD_AES_256_GCM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KdfId: dicttls.HKDF_SHA256,
|
||||||
|
AeadId: dicttls.AEAD_CHACHA20_POLY1305,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CandidatePayloadLens: []uint16{128, 160}, // +16: 144, 176
|
||||||
|
},
|
||||||
|
&UtlsGREASEExtension{},
|
||||||
|
}),
|
||||||
|
}, nil
|
||||||
case HelloFirefox_55, HelloFirefox_56:
|
case HelloFirefox_55, HelloFirefox_56:
|
||||||
return ClientHelloSpec{
|
return ClientHelloSpec{
|
||||||
TLSVersMax: VersionTLS12,
|
TLSVersMax: VersionTLS12,
|
||||||
|
@ -1043,6 +1135,121 @@ func utlsIdToSpec(id ClientHelloID) (ClientHelloSpec, error) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
|
case HelloFirefox_120:
|
||||||
|
return ClientHelloSpec{
|
||||||
|
TLSVersMin: VersionTLS12,
|
||||||
|
TLSVersMax: VersionTLS13,
|
||||||
|
CipherSuites: []uint16{
|
||||||
|
TLS_AES_128_GCM_SHA256,
|
||||||
|
TLS_CHACHA20_POLY1305_SHA256,
|
||||||
|
TLS_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
|
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
|
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
},
|
||||||
|
CompressionMethods: []uint8{
|
||||||
|
0x0, // no compression
|
||||||
|
},
|
||||||
|
Extensions: []TLSExtension{
|
||||||
|
&SNIExtension{},
|
||||||
|
&ExtendedMasterSecretExtension{},
|
||||||
|
&RenegotiationInfoExtension{
|
||||||
|
Renegotiation: RenegotiateOnceAsClient,
|
||||||
|
},
|
||||||
|
&SupportedCurvesExtension{
|
||||||
|
Curves: []CurveID{
|
||||||
|
X25519,
|
||||||
|
CurveP256,
|
||||||
|
CurveP384,
|
||||||
|
CurveP521,
|
||||||
|
256,
|
||||||
|
257,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&SupportedPointsExtension{
|
||||||
|
SupportedPoints: []uint8{
|
||||||
|
0x0, // uncompressed
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ALPNExtension{
|
||||||
|
AlpnProtocols: []string{
|
||||||
|
"h2",
|
||||||
|
"http/1.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&StatusRequestExtension{},
|
||||||
|
&FakeDelegatedCredentialsExtension{
|
||||||
|
SupportedSignatureAlgorithms: []SignatureScheme{
|
||||||
|
ECDSAWithP256AndSHA256,
|
||||||
|
ECDSAWithP384AndSHA384,
|
||||||
|
ECDSAWithP521AndSHA512,
|
||||||
|
ECDSAWithSHA1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&KeyShareExtension{
|
||||||
|
KeyShares: []KeyShare{
|
||||||
|
{
|
||||||
|
Group: X25519,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Group: CurveP256,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&SupportedVersionsExtension{
|
||||||
|
Versions: []uint16{
|
||||||
|
VersionTLS13,
|
||||||
|
VersionTLS12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&SignatureAlgorithmsExtension{
|
||||||
|
SupportedSignatureAlgorithms: []SignatureScheme{
|
||||||
|
ECDSAWithP256AndSHA256,
|
||||||
|
ECDSAWithP384AndSHA384,
|
||||||
|
ECDSAWithP521AndSHA512,
|
||||||
|
PSSWithSHA256,
|
||||||
|
PSSWithSHA384,
|
||||||
|
PSSWithSHA512,
|
||||||
|
PKCS1WithSHA256,
|
||||||
|
PKCS1WithSHA384,
|
||||||
|
PKCS1WithSHA512,
|
||||||
|
ECDSAWithSHA1,
|
||||||
|
PKCS1WithSHA1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&FakeRecordSizeLimitExtension{
|
||||||
|
Limit: 0x4001,
|
||||||
|
},
|
||||||
|
&GREASEEncryptedClientHelloExtension{
|
||||||
|
CandidateCipherSuites: []HPKESymmetricCipherSuite{
|
||||||
|
{
|
||||||
|
KdfId: dicttls.HKDF_SHA256,
|
||||||
|
AeadId: dicttls.AEAD_AES_128_GCM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KdfId: dicttls.HKDF_SHA256,
|
||||||
|
AeadId: dicttls.AEAD_AES_256_GCM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
KdfId: dicttls.HKDF_SHA256,
|
||||||
|
AeadId: dicttls.AEAD_CHACHA20_POLY1305,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CandidatePayloadLens: []uint16{128, 223}, // +16: 144, 239
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
case HelloIOS_11_1:
|
case HelloIOS_11_1:
|
||||||
return ClientHelloSpec{
|
return ClientHelloSpec{
|
||||||
TLSVersMax: VersionTLS12,
|
TLSVersMax: VersionTLS12,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue