diff --git a/Makefile b/Makefile index ff1a6b5..144d4a3 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ testinputs: util ${UTIL} -make-intermediate -cert-in ${TESTDATA_DIR}/root.crt -key-in ${TESTDATA_DIR}/root.key -out ${TESTDATA_DIR}/client-facing.crt -key-out ${TESTDATA_DIR}/client-facing.key -host client-facing.com ${UTIL} -make-dc -cert-in ${TESTDATA_DIR}/example.crt -key-in ${TESTDATA_DIR}/example.key -out ${TESTDATA_DIR}/dc.txt ${UTIL} -make-ech -out ${TESTDATA_DIR}/ech_configs -key-out ${TESTDATA_DIR}/ech_key -host client-facing.com + ${UTIL} -make-ech -out ${TESTDATA_DIR}/ech_configs_invalid -key-out /dev/null -host client-facing.com clean: rm -fr ${BIN_DIR} diff --git a/impl-endpoints/cloudflare-go/run_endpoint.sh b/impl-endpoints/cloudflare-go/run_endpoint.sh index b3e331b..60f1ce5 100644 --- a/impl-endpoints/cloudflare-go/run_endpoint.sh +++ b/impl-endpoints/cloudflare-go/run_endpoint.sh @@ -9,12 +9,10 @@ sh /setup-routes.sh if [ "$ROLE" = "client" ]; then echo "Running Cloudflare-Go client." - echo "Client params: $CLIENT_PARAMS" echo "Test case: $TESTCASE" runner -role=client -test=${TESTCASE} else echo "Running Cloudflare-Go server." - echo "Server params: $SERVER_PARAMS" echo "Test case: $TESTCASE" runner -role=server -test=${TESTCASE} fi diff --git a/impl-endpoints/cloudflare-go/runner.go b/impl-endpoints/cloudflare-go/runner.go index d994512..5479b9f 100644 --- a/impl-endpoints/cloudflare-go/runner.go +++ b/impl-endpoints/cloudflare-go/runner.go @@ -9,6 +9,7 @@ import ( "encoding/base64" "errors" "flag" + "fmt" "io/ioutil" "log" "net" @@ -17,9 +18,9 @@ import ( type testCase interface { ClientConfig() (*tls.Config, error) - ClientConnectionHandler(*tls.Conn) error + ClientConnectionHandler(*tls.Conn, error) error ServerConfig() (*tls.Config, error) - ServerConnectionHandler(*tls.Conn) error + ServerConnectionHandler(*tls.Conn, error) error } var baseServerConfig, baseClientConfig *tls.Config @@ -38,7 +39,7 @@ func init() { certPool := x509.NewCertPool() certPool.AppendCertsFromPEM(pem) baseClientConfig = &tls.Config{ - RootCAs: certPool, + RootCAs: certPool, KeyLogWriter: clientKeyLog, } @@ -72,6 +73,8 @@ func init() { } } +// The "dc" test case in which the client indicates support for DCs and the +// server uses a DC for authentication. See draft-ietf-tls-subcerts. type testCaseDC struct{} func (t testCaseDC) ClientConfig() (*tls.Config, error) { @@ -85,55 +88,102 @@ func (t testCaseDC) ServerConfig() (*tls.Config, error) { return config, nil } -func (t testCaseDC) ClientConnectionHandler(conn *tls.Conn) error { +func (t testCaseDC) ClientConnectionHandler(conn *tls.Conn, err error) error { + if err != nil { + return fmt.Errorf("Unexpected error: \"%s\"", err) + } if conn.ConnectionState().VerifiedDC == nil { return errors.New("Failed to verify a delegated credential") } return nil } -func (t testCaseDC) ServerConnectionHandler(conn *tls.Conn) error { +func (t testCaseDC) ServerConnectionHandler(conn *tls.Conn, err error) error { // TODO return errors.New("NOT IMPLEMENTED") } -type echTestResult struct { - serverStatus tls.EXP_EventECHServerStatus -} +// The "ech-accept" test case in which a client offers the ECH extension and +// the server accepts. The client verifies server name "backend.com". See +// draft-ietf-tls-esni. +// +// TODO(cjpatton): Check via validatepcap that: +// (1) client sent ECH in CH; and +// (2) SNI = "example.com". +type testCaseECHAccept struct{} -func (r *echTestResult) eventHandler(event tls.EXP_Event) { - switch e := event.(type) { - case tls.EXP_EventECHServerStatus: - r.serverStatus = e - } +func (t testCaseECHAccept) ClientConfig() (*tls.Config, error) { + return echClientConfig("/test-inputs/ech_configs") } -type testCaseECHAccept struct{} +func (t testCaseECHAccept) ServerConfig() (*tls.Config, error) { + return echServerConfig("/test-inputs/ech_key") +} -func (t testCaseECHAccept) ClientConfig() (*tls.Config, error) { - base64ECHConfigs, err := ioutil.ReadFile("/test-inputs/ech_configs") +func (t testCaseECHAccept) connectionHandler(conn *tls.Conn, err error) error { if err != nil { - return nil, err + return fmt.Errorf("Unexpected error: \"%s\"", err) } - rawECHConfigs, err := base64.StdEncoding.DecodeString(string(base64ECHConfigs)) - if err != nil { - return nil, err + st := conn.ConnectionState() + if !st.ECHAccepted { + return errors.New("ECH not accepted") } + log.Printf("Connection completed with backend server \"%s\"", st.ServerName) + return nil +} - echConfigs, err := tls.UnmarshalECHConfigs(rawECHConfigs) - if err != nil { - return nil, err +func (t testCaseECHAccept) ClientConnectionHandler(conn *tls.Conn, err error) error { + return t.connectionHandler(conn, err) +} + +func (t testCaseECHAccept) ServerConnectionHandler(conn *tls.Conn, err error) error { + return t.connectionHandler(conn, err) +} + +// The "ech-reject" test case in which the client offers ECH with stale configs +// and the server rejects. The client verifies server name "client-facing.com", +// then aborts the handshake with an "ech_required" alert. See +// draft-ietf-tls-esni. +// +// TODO(cjpatton): Check via validatepcap that: +// (1) client sent ECH in CH; +// (2) server sent ECH in EE; and +// (3) SNI = "client-facing.com". +type testCaseECHReject struct{} + +func (t testCaseECHReject) ClientConfig() (*tls.Config, error) { + return echClientConfig("/test-inputs/ech_configs_invalid") +} + +func (t testCaseECHReject) ServerConfig() (*tls.Config, error) { + return echServerConfig("/test-inputs/ech_key") +} + +func (t testCaseECHReject) ClientConnectionHandler(conn *tls.Conn, err error) error { + if err == nil { + return errors.New("Handshake completed successfully; expected error") + } + if err.Error() != "tls: ech: rejected" { + return fmt.Errorf("Unexpected error: \"%s\"", err) } + log.Print("Aborted the connection as expected") + return nil +} - config := baseClientConfig.Clone() - config.ClientECHConfigs = echConfigs - config.ECHEnabled = true - return config, nil +func (t testCaseECHReject) ServerConnectionHandler(conn *tls.Conn, err error) error { + if err != nil { + return fmt.Errorf("Unexpected error: \"%s\"", err) + } + if conn.ConnectionState().ECHAccepted { + return errors.New("ECH accepted; expected rejection") + } + log.Printf("Connection completed with client-facing server \"%s\"", conn.ConnectionState().ServerName) + return nil } -func (t testCaseECHAccept) ServerConfig() (*tls.Config, error) { - base64ECHKeys, err := ioutil.ReadFile("/test-inputs/ech_key") +func echServerConfig(keysFile string) (*tls.Config, error) { + base64ECHKeys, err := ioutil.ReadFile(keysFile) if err != nil { return nil, err } @@ -159,24 +209,32 @@ func (t testCaseECHAccept) ServerConfig() (*tls.Config, error) { return config, nil } -func (t testCaseECHAccept) connectionHandler(conn *tls.Conn) error { - if !conn.ConnectionState().ECHAccepted { - return errors.New("ECH not accepted") +func echClientConfig(configsFile string) (*tls.Config, error) { + base64ECHConfigs, err := ioutil.ReadFile(configsFile) + if err != nil { + return nil, err } - return nil -} -func (t testCaseECHAccept) ClientConnectionHandler(conn *tls.Conn) error { - return t.connectionHandler(conn) -} + rawECHConfigs, err := base64.StdEncoding.DecodeString(string(base64ECHConfigs)) + if err != nil { + return nil, err + } + + echConfigs, err := tls.UnmarshalECHConfigs(rawECHConfigs) + if err != nil { + return nil, err + } -func (t testCaseECHAccept) ServerConnectionHandler(conn *tls.Conn) error { - return t.connectionHandler(conn) + config := baseClientConfig.Clone() + config.ClientECHConfigs = echConfigs + config.ECHEnabled = true + return config, nil } var testCaseHandlers = map[string]testCase{ "dc": testCaseDC{}, "ech-accept": testCaseECHAccept{}, + "ech-reject": testCaseECHReject{}, } func doClient(t testCase) error { @@ -186,13 +244,10 @@ func doClient(t testCase) error { } c, err := tls.Dial("tcp", "example.com:4433", config) - if err != nil { - return err + if err == nil { + defer c.Close() } - defer c.Close() - - log.Print("Handshake completed") - return t.ClientConnectionHandler(c) + return t.ClientConnectionHandler(c, err) } func doServer(t testCase) error { @@ -215,13 +270,10 @@ func doServer(t testCase) error { s := tls.Server(conn, config) err = s.Handshake() - if err != nil { - return err + if err == nil { + defer s.Close() } - defer s.Close() - - log.Print("Handshake completed") - return t.ServerConnectionHandler(s) + return t.ServerConnectionHandler(s, err) } func main() { @@ -235,15 +287,16 @@ func main() { os.Exit(64) } + log.SetFlags(0) if *role == "client" { if err := doClient(handler); err != nil { - log.Fatal("Error: ", err) + log.Fatal(err) } } else if *role == "server" { if err := doServer(handler); err != nil { - log.Fatal("Error: ", err) + log.Fatal(err) } } else { - log.Fatalf("unknown role \"%s\"", *role) + log.Fatalf("Unknown role \"%s\"", *role) } } diff --git a/impl-endpoints/nss/run_endpoint.sh b/impl-endpoints/nss/run_endpoint.sh index c211018..329c730 100644 --- a/impl-endpoints/nss/run_endpoint.sh +++ b/impl-endpoints/nss/run_endpoint.sh @@ -13,7 +13,7 @@ PORT=4433 rm -rf nss_testdata mkdir nss_testdata -if [ "$TESTCASE" = "ech-accept" ]; then +if [ "$TESTCASE" = "ech-accept" ] || [ "$TESTCASE" = "ech-reject" ]; then # Create a PKCS8 file for the ECH keypair python3 /ech_key_converter.py /test-inputs/ech_key nss_testdata/ech_key_converted @@ -42,9 +42,24 @@ if [ "$ROLE" = "client" ]; then echo "Running NSS client." echo "Client params: $SERVER_PARAMS" echo "Test case: $TESTCASE" - ECH_CONFIGS=$( req.txt - tstclnt -d "$DB_DIR" -h example.com -p "$PORT" -N "$ECH_CONFIGS" -A req.txt + if [ "$TESTCASE" = "ech-reject" ]; then + ECH_CONFIGS=$( err.txt || true + ECH_CONFIGS=$(sed '4q;d' err.txt) + if [ "$ECH_CONFIGS" != "$(