|
package main |
|
|
|
import ( |
|
"context" |
|
"crypto/tls" |
|
"crypto/x509" |
|
"encoding/pem" |
|
"fmt" |
|
"io/ioutil" |
|
"log" |
|
"net" |
|
"net/http" |
|
"os" |
|
) |
|
|
|
const echConfigsListString = `-----BEGIN ECH CONFIGS----- |
|
AEb+DQBCGwAgACDSupslkfIkg/C0be/yDdZqtUJs4ssKG5IgWHadWXn4KQAEAAEA |
|
ASUTY2xvdWRmbGFyZS1lc25pLmNvbQAA |
|
-----END ECH CONFIGS-----` |
|
|
|
func main() { |
|
block, rest := pem.Decode([]byte(echConfigsListString)) |
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 { |
|
log.Fatal("Failet to PEM-decode the ECH configs") |
|
} |
|
|
|
echConfigsList, err := tls.UnmarshalECHConfigs(block.Bytes) |
|
if err != nil { |
|
log.Fatal("Failed to parse ECH configs:", err) |
|
} |
|
|
|
rootData, err := ioutil.ReadFile("root.crt") |
|
if err != nil { |
|
log.Fatal("Failed to load root cert:", err) |
|
} |
|
|
|
block, rest = pem.Decode(rootData) |
|
if block == nil || block.Type != "CERTIFICATE" || len(rest) > 0 { |
|
log.Fatal("Failed to PEM-decode the root cert") |
|
} |
|
|
|
root, err := x509.ParseCertificate(block.Bytes) |
|
if err != nil { |
|
log.Fatal("failed to parse root cert:", err) |
|
} |
|
rootCAs := x509.NewCertPool() |
|
rootCAs.AddCert(root) |
|
|
|
config := &tls.Config{ |
|
ServerName: "example.com", // This SNI is protected by ECH. |
|
ECHEnabled: true, |
|
ClientECHConfigs: echConfigsList, |
|
MinVersion: tls.VersionTLS13, |
|
RootCAs: rootCAs, |
|
} |
|
|
|
if tlsKeyLogFile := os.Getenv("SSLKEYLOGFILE"); tlsKeyLogFile != "" { |
|
kw, err := os.OpenFile(tlsKeyLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) |
|
if err != nil { |
|
log.Printf("Cannot open key log file: %s\n", err) |
|
} |
|
config.KeyLogWriter = kw |
|
} |
|
|
|
req, err := http.NewRequest("GET", "https://example.com/hello", nil) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
|
|
client := &http.Client{ |
|
Transport: &http.Transport{ |
|
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { |
|
return tls.Dial(network, ":8080", config) |
|
}, |
|
}, |
|
} |
|
resp, err := client.Do(req) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
|
|
out, err := ioutil.ReadAll(resp.Body) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
|
|
fmt.Printf("%v %v %v\n\n", resp.Status, resp.Proto, resp.ContentLength) |
|
for name, val := range resp.Header { |
|
fmt.Printf("%v: %v\n", name, val) |
|
} |
|
fmt.Println() |
|
fmt.Printf("%v\n", string(out)) |
|
} |