From 82d3ae6b95d4ef84f3a25087411ae8367261cdc7 Mon Sep 17 00:00:00 2001 From: Sofia Scalzo Date: Fri, 17 Jul 2026 02:42:29 -0700 Subject: [PATCH] add smoke-test client and standalone KE server for chrony interop Summary: Adds two dev/test binaries under `time/ntp/ntske/cmd/` to exercise the NTS-KE server end-to-end. `ntsketest` is a smoke-test client: it dials the KE server over TLS 1.3 with ALPN `ntske/1`, sends a NextProtocol=NTPv4 + AEAD `[30, 17]` + `RecordCompliant128GCMExport` request, parses the response, and prints `[ke] PASS: next-proto=NTPv4 aead= cookies=`. Flags: `--addr`, `--ca` (trust a self-signed dev cert), `--skip-ntp` (stop after the KE handshake, since the NTPv4 phase is not implemented yet), and `--timeout`. It mirrors the server's unexported record reader because the `ntske` package exposes no reader-based helper, and re-uses the exported `MarshalRecords`/`Parse` and record constructors. `ntskeserver` is a thin standalone server: it loads a cert/key with `tls.LoadX509KeyPair`, builds an `InMemoryKeystore`, and runs `ntske.Server.ListenAndServe` until SIGINT/SIGTERM. It is the counterpart the `ntsketest` client and `chronyd` connect to for local interop testing. Reviewed By: leoleovich Differential Revision: D111491705 --- ntp/ntske/client.go | 226 ++++++++++++++++++++++++++++++ ntp/ntske/client_test.go | 218 ++++++++++++++++++++++++++++ ntp/ntske/cmd/ntskeserver/main.go | 72 ++++++++++ ntp/ntske/cmd/ntsketest/main.go | 65 +++++++++ ntp/ntske/server.go | 2 +- ntp/ntske/server_test.go | 6 +- 6 files changed, 585 insertions(+), 4 deletions(-) create mode 100644 ntp/ntske/client.go create mode 100644 ntp/ntske/client_test.go create mode 100644 ntp/ntske/cmd/ntskeserver/main.go create mode 100644 ntp/ntske/cmd/ntsketest/main.go diff --git a/ntp/ntske/client.go b/ntp/ntske/client.go new file mode 100644 index 00000000..b68a3932 --- /dev/null +++ b/ntp/ntske/client.go @@ -0,0 +1,226 @@ +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ntske + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io" + "os" + "slices" + "time" + + "github.com/facebook/time/ntp/protocol" +) + +// Client performs the client side of an NTS-KE handshake against a Server. +type Client struct { + // SupportedAEAD is the client's AEAD preference list, most preferred first. + // Defaults to [AES-128-GCM-SIV (30), AES-SIV-CMAC-512 (17)] when empty. + SupportedAEAD []uint16 + // RequestCompliantExport, when true, offers chrony's + // compliant-128-GCM-SIV-export record in the request. + RequestCompliantExport bool + // Timeout bounds the whole exchange (dial + TLS + record read). + // Defaults to defaultHandshakeTimeout when zero. + Timeout time.Duration +} + +// HandshakeResult is the validated outcome of a successful NTS-KE exchange. +type HandshakeResult struct { + // NextProtocol is the negotiated next protocol (NextProtocolNTPv4). + NextProtocol uint16 + // AEAD is the negotiated AEAD algorithm ID. + AEAD uint16 + // Cookies are the NTS cookies the server issued. + Cookies [][]byte + // CompliantExport reports whether the server echoed the chrony + // compliant-128-GCM-SIV-export record. + CompliantExport bool +} + +// ClientTLSConfig builds a TLS 1.3 client config for NTS-KE. When caFile is +// non-empty, only that PEM is trusted (for a self-signed dev cert); otherwise +// the system roots are used. +func ClientTLSConfig(caFile string) (*tls.Config, error) { + conf := &tls.Config{ + MinVersion: tls.VersionTLS13, + NextProtos: []string{ALPNProtocol}, + } + if caFile == "" { + return conf, nil + } + pem, err := os.ReadFile(caFile) + if err != nil { + return nil, fmt.Errorf("read ca %q: %w", caFile, err) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(pem) { + return nil, fmt.Errorf("ca %q: no valid certificate found", caFile) + } + conf.RootCAs = pool + return conf, nil +} + +// Handshake dials addr over TLS 1.3, runs the NTS-KE exchange, and returns the +// validated result. tlsConf is cloned; MinVersion is pinned to TLS 1.3 and ALPN +// to "ntske/1" so callers cannot accidentally weaken the transport. +func (c *Client) Handshake(ctx context.Context, addr string, tlsConf *tls.Config) (*HandshakeResult, error) { + if tlsConf == nil { + return nil, errors.New("ntske: TLSConfig is required") + } + tlsConf = tlsConf.Clone() + tlsConf.MinVersion = tls.VersionTLS13 + tlsConf.NextProtos = []string{ALPNProtocol} + + ctx, cancel := context.WithTimeout(ctx, c.timeout()) + defer cancel() + + dialer := tls.Dialer{Config: tlsConf} + conn, err := dialer.DialContext(ctx, "tcp", addr) + if err != nil { + return nil, fmt.Errorf("ntske: dial %q: %w", addr, err) + } + defer func() { _ = conn.Close() }() + + tlsConn := conn.(*tls.Conn) + if deadline, ok := ctx.Deadline(); ok { + _ = tlsConn.SetDeadline(deadline) + } + if proto := tlsConn.ConnectionState().NegotiatedProtocol; proto != ALPNProtocol { + return nil, fmt.Errorf("ntske: server negotiated ALPN %q, want %q", proto, ALPNProtocol) + } + + if err := c.writeRequest(tlsConn); err != nil { + return nil, fmt.Errorf("ntske: write request: %w", err) + } + records, err := readMessage(tlsConn, maxMessageSize) + if err != nil { + return nil, fmt.Errorf("ntske: read response: %w", err) + } + return c.interpret(records) +} + +// writeRequest sends the NTS-KE request: Next Protocol NTPv4, the AEAD +// preference list, optionally the compliant-export record, and End of Message. +func (c *Client) writeRequest(w io.Writer) error { + records := []Record{ + NewNextProtocol(NextProtocolNTPv4), + NewAEADAlgorithm(c.supportedAEAD()...), + } + if c.RequestCompliantExport { + records = append(records, Record{Type: RecordCompliant128GCMExport}) + } + records = append(records, NewEndOfMessage()) + + b, err := MarshalRecords(records) + if err != nil { + return err + } + _, err = w.Write(b) + return err +} + +// interpret walks the response records, enforces the mandatory fields, and +// validates that the negotiated AEAD is one the client actually offered. +func (c *Client) interpret(records []Record) (*HandshakeResult, error) { + var ( + res HandshakeResult + sawNextProto bool + sawAEAD bool + ) + for _, r := range records { + switch r.Type { + case RecordError: + code, err := ParseUint16s(r.Body) + if err != nil { + return nil, fmt.Errorf("server Error record with malformed body: %w", err) + } + return nil, fmt.Errorf("server returned Error record, code=%v", code) + case RecordNextProtocol: + ids, err := ParseUint16s(r.Body) + if err != nil { + return nil, fmt.Errorf("malformed Next Protocol body: %w", err) + } + if len(ids) == 0 { + return nil, errors.New("empty Next Protocol record") + } + res.NextProtocol = ids[0] + sawNextProto = true + case RecordAEADAlgorithm: + ids, err := ParseUint16s(r.Body) + if err != nil { + return nil, fmt.Errorf("malformed AEAD body: %w", err) + } + if len(ids) == 0 { + return nil, errors.New("empty AEAD record") + } + res.AEAD = ids[0] + sawAEAD = true + case RecordNewCookie: + res.Cookies = append(res.Cookies, r.Body) + case RecordCompliant128GCMExport: + res.CompliantExport = true + } + } + + switch { + case !sawNextProto: + return nil, errors.New("response missing Next Protocol record") + case res.NextProtocol != NextProtocolNTPv4: + return nil, fmt.Errorf("server selected next-proto %d, want NTPv4 (%d)", res.NextProtocol, NextProtocolNTPv4) + case !sawAEAD: + return nil, errors.New("response missing AEAD record") + case !slices.Contains(c.supportedAEAD(), res.AEAD): + return nil, fmt.Errorf("server selected AEAD %d not offered by client", res.AEAD) + case len(res.Cookies) == 0: + return nil, errors.New("response contained no cookies") + } + return &res, nil +} + +// supportedAEAD returns the configured preference list or the default +// [AES-128-GCM-SIV (30), AES-SIV-CMAC-512 (17)] when unset. +func (c *Client) supportedAEAD() []uint16 { + if len(c.SupportedAEAD) > 0 { + return c.SupportedAEAD + } + return []uint16{ + uint16(protocol.AEADAES128GCMSIV), // 30 + uint16(protocol.AEADAESSIVCMAC512), // 17 + } +} + +// timeout returns the configured timeout or defaultHandshakeTimeout when unset. +func (c *Client) timeout() time.Duration { + if c.Timeout > 0 { + return c.Timeout + } + return defaultHandshakeTimeout +} + +// NextProtocolName maps an NTS-KE Next Protocol ID to a human-readable name, +// falling back to "unknown()" for anything other than NTPv4. +func NextProtocolName(id uint16) string { + if id == NextProtocolNTPv4 { + return "NTPv4" + } + return fmt.Sprintf("unknown(%d)", id) +} diff --git a/ntp/ntske/client_test.go b/ntp/ntske/client_test.go new file mode 100644 index 00000000..8d295525 --- /dev/null +++ b/ntp/ntske/client_test.go @@ -0,0 +1,218 @@ +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package ntske + +import ( + "context" + "crypto/ed25519" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "os" + "path/filepath" + "testing" + "time" + + "github.com/facebook/time/ntp/protocol" + "github.com/stretchr/testify/require" +) + +// clientTestCert returns a self-signed Ed25519 cert/key (PEM) valid for +// 127.0.0.1, so the real client can verify it against a CA file. +func clientTestCert(t *testing.T) (certPEM, keyPEM []byte) { + t.Helper() + pub, priv, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "127.0.0.1"}, + NotBefore: time.Unix(0, 0), + NotAfter: time.Unix(1<<31-1, 0), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) + require.NoError(t, err) + keyDER, err := x509.MarshalPKCS8PrivateKey(priv) + require.NoError(t, err) + certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) + keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) + return certPEM, keyPEM +} + +// clientFreePort binds an ephemeral port, closes it, and returns the address so +// a server can listen on it. +func clientFreePort(t *testing.T) string { + t.Helper() + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + addr := ln.Addr().String() + require.NoError(t, ln.Close()) + return addr +} + +// startTestKEServer spins up an in-process Server on an ephemeral port and +// returns its address plus the CA PEM the client should trust. +func startTestKEServer(t *testing.T, cookies uint16) (addr string, caPEM []byte) { + t.Helper() + certPEM, keyPEM := clientTestCert(t) + cert, err := tls.X509KeyPair(certPEM, keyPEM) + require.NoError(t, err) + ks, err := NewInMemoryKeystore(InMemoryKeystoreOptions{}) + require.NoError(t, err) + srv := &Server{ + TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, + Keystore: ks, + Cookies: cookies, + } + addr = clientFreePort(t) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + go func() { _ = srv.ListenAndServe(ctx, addr) }() + + // wait until the listener accepts before returning + deadline := time.Now().Add(3 * time.Second) + for time.Now().Before(deadline) { + c, derr := net.DialTimeout("tcp", addr, 200*time.Millisecond) + if derr == nil { + _ = c.Close() + return addr, certPEM + } + time.Sleep(20 * time.Millisecond) + } + t.Fatalf("server at %s never became ready", addr) + return "", nil +} + +func TestClientHandshake(t *testing.T) { + addr, caPEM := startTestKEServer(t, 8) + caFile := filepath.Join(t.TempDir(), "ca.pem") + require.NoError(t, os.WriteFile(caFile, caPEM, 0o600)) + + tlsConf, err := ClientTLSConfig(caFile) + require.NoError(t, err) + + res, err := (&Client{}).Handshake(context.Background(), addr, tlsConf) + require.NoError(t, err) + require.Equal(t, NextProtocolNTPv4, res.NextProtocol) + require.Equal(t, uint16(protocol.AEADAES128GCMSIV), res.AEAD) + require.Len(t, res.Cookies, 8) +} + +func TestClientHandshakeUntrustedCert(t *testing.T) { + addr, _ := startTestKEServer(t, 8) + otherPEM, _ := clientTestCert(t) // a CA that does not contain the server's cert + caFile := filepath.Join(t.TempDir(), "other.pem") + require.NoError(t, os.WriteFile(caFile, otherPEM, 0o600)) + + tlsConf, err := ClientTLSConfig(caFile) + require.NoError(t, err) + _, err = (&Client{}).Handshake(context.Background(), addr, tlsConf) + require.Error(t, err) +} + +func TestClientTLSConfig(t *testing.T) { + t.Run("no CA uses system roots", func(t *testing.T) { + conf, err := ClientTLSConfig("") + require.NoError(t, err) + require.Nil(t, conf.RootCAs) + require.Equal(t, uint16(tls.VersionTLS13), conf.MinVersion) + require.Equal(t, []string{ALPNProtocol}, conf.NextProtos) + }) + t.Run("valid CA", func(t *testing.T) { + certPEM, _ := clientTestCert(t) + caFile := filepath.Join(t.TempDir(), "ca.pem") + require.NoError(t, os.WriteFile(caFile, certPEM, 0o600)) + conf, err := ClientTLSConfig(caFile) + require.NoError(t, err) + require.NotNil(t, conf.RootCAs) + }) + t.Run("missing file", func(t *testing.T) { + _, err := ClientTLSConfig(filepath.Join(t.TempDir(), "nope.pem")) + require.Error(t, err) + }) + t.Run("garbage PEM", func(t *testing.T) { + caFile := filepath.Join(t.TempDir(), "junk.pem") + require.NoError(t, os.WriteFile(caFile, []byte("not a pem"), 0o600)) + _, err := ClientTLSConfig(caFile) + require.Error(t, err) + }) +} + +func TestClientInterpret(t *testing.T) { + cookie := func(n byte) Record { return NewCookie([]byte{n, n, n, n}) } + c := &Client{} // default offers [30, 17] + + t.Run("valid", func(t *testing.T) { + res, err := c.interpret([]Record{ + NewNextProtocol(NextProtocolNTPv4), + NewAEADAlgorithm(uint16(protocol.AEADAES128GCMSIV)), + cookie(1), cookie(2), + }) + require.NoError(t, err) + require.Equal(t, uint16(protocol.AEADAES128GCMSIV), res.AEAD) + require.Len(t, res.Cookies, 2) + require.False(t, res.CompliantExport) + }) + t.Run("compliant export echoed", func(t *testing.T) { + res, err := c.interpret([]Record{ + NewNextProtocol(NextProtocolNTPv4), + NewAEADAlgorithm(uint16(protocol.AEADAES128GCMSIV)), + {Type: RecordCompliant128GCMExport}, + cookie(1), + }) + require.NoError(t, err) + require.True(t, res.CompliantExport) + }) + t.Run("server error record", func(t *testing.T) { + _, err := c.interpret([]Record{NewError(1)}) + require.Error(t, err) + }) + t.Run("missing next-proto", func(t *testing.T) { + _, err := c.interpret([]Record{ + NewAEADAlgorithm(uint16(protocol.AEADAES128GCMSIV)), cookie(1), + }) + require.Error(t, err) + }) + t.Run("wrong next-proto", func(t *testing.T) { + _, err := c.interpret([]Record{ + NewNextProtocol(7), + NewAEADAlgorithm(uint16(protocol.AEADAES128GCMSIV)), cookie(1), + }) + require.Error(t, err) + }) + t.Run("unoffered AEAD rejected", func(t *testing.T) { + _, err := c.interpret([]Record{ + NewNextProtocol(NextProtocolNTPv4), + NewAEADAlgorithm(9999), // not in [30, 17] + cookie(1), + }) + require.Error(t, err) + }) + t.Run("no cookies", func(t *testing.T) { + _, err := c.interpret([]Record{ + NewNextProtocol(NextProtocolNTPv4), + NewAEADAlgorithm(uint16(protocol.AEADAES128GCMSIV)), + }) + require.Error(t, err) + }) +} diff --git a/ntp/ntske/cmd/ntskeserver/main.go b/ntp/ntske/cmd/ntskeserver/main.go new file mode 100644 index 00000000..68bae181 --- /dev/null +++ b/ntp/ntske/cmd/ntskeserver/main.go @@ -0,0 +1,72 @@ +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Command ntskeserver is a standalone NTS-KE server for local interop testing. +// +// ntskeserver --addr 127.0.0.1:4460 --cert /tmp/ntske_cert.pem --key /tmp/ntske_key.pem +package main + +import ( + "context" + "crypto/tls" + "flag" + "log/slog" + "math" + "os" + "os/signal" + "syscall" + + "github.com/facebook/time/ntp/ntske" +) + +func main() { + addr := flag.String("addr", "127.0.0.1:4460", "address to listen on (host:port)") + certFile := flag.String("cert", "/tmp/ntske_cert.pem", "TLS certificate PEM") + keyFile := flag.String("key", "/tmp/ntske_key.pem", "TLS private key PEM") + cookies := flag.Uint("cookies", 8, "number of cookies to issue per handshake") + flag.Parse() + if *cookies > math.MaxUint16 { + slog.Error("invalid --cookies value exceeds uint16 range", "value", *cookies, "max", math.MaxUint16) + os.Exit(1) + } + + cert, err := tls.LoadX509KeyPair(*certFile, *keyFile) + if err != nil { + slog.Error("load cert/key", "err", err) + os.Exit(1) + } + + keystore, err := ntske.NewInMemoryKeystore(ntske.InMemoryKeystoreOptions{}) + if err != nil { + slog.Error("keystore", "err", err) + os.Exit(1) + } + + srv := &ntske.Server{ + TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}}, + Keystore: keystore, + Cookies: uint16(*cookies), //nolint:gosec // bounded: math.MaxUint16 (65535) guard above exits + } + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + slog.Info("NTS-KE server listening", "addr", *addr, "cookies", *cookies) + if err := srv.ListenAndServe(ctx, *addr); err != nil { + slog.Error("serve", "err", err) + os.Exit(1) + } +} diff --git a/ntp/ntske/cmd/ntsketest/main.go b/ntp/ntske/cmd/ntsketest/main.go new file mode 100644 index 00000000..5750d1c3 --- /dev/null +++ b/ntp/ntske/cmd/ntsketest/main.go @@ -0,0 +1,65 @@ +/* +Copyright (c) Facebook, Inc. and its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Command ntsketest is a smoke-test client for the NTS-KE server. It performs a +// single handshake via ntske.Client and prints a PASS line with the negotiated +// next-protocol, AEAD algorithm, and cookie count. +// +// ntsketest --addr 127.0.0.1:4460 --ca /tmp/ntske_cert.pem --skip-ntp +// [ke] PASS: next-proto=NTPv4 aead=30 cookies=8 +package main + +import ( + "context" + "flag" + "fmt" + "os" + "time" + + "github.com/facebook/time/ntp/ntske" +) + +func main() { + addr := flag.String("addr", "127.0.0.1:4460", "NTS-KE server address (host:port)") + caFile := flag.String("ca", "", "PEM file with the CA/self-signed cert to trust (empty = system roots)") + skipNTP := flag.Bool("skip-ntp", false, "stop after the NTS-KE handshake and do not attempt the NTPv4 phase") + timeout := flag.Duration("timeout", 10*time.Second, "overall timeout for the handshake") + flag.Parse() + + tlsConf, err := ntske.ClientTLSConfig(*caFile) + if err != nil { + fmt.Fprintf(os.Stderr, "[ke] FAIL: %v\n", err) + os.Exit(1) + } + + client := &ntske.Client{RequestCompliantExport: true, Timeout: *timeout} + res, err := client.Handshake(context.Background(), *addr, tlsConf) + if err != nil { + fmt.Fprintf(os.Stderr, "[ke] FAIL: %v\n", err) + os.Exit(1) + } + + fmt.Printf("[ke] PASS: next-proto=%s aead=%d cookies=%d\n", + ntske.NextProtocolName(res.NextProtocol), res.AEAD, len(res.Cookies)) + if res.CompliantExport { + fmt.Println("[ke] compliant-128-GCM-SIV-export negotiated") + } + + if !*skipNTP { + fmt.Fprintln(os.Stderr, "[ke] note: NTPv4 phase not implemented in milestone 1; re-run with --skip-ntp") + os.Exit(1) + } +} diff --git a/ntp/ntske/server.go b/ntp/ntske/server.go index cd0defe7..95222f47 100644 --- a/ntp/ntske/server.go +++ b/ntp/ntske/server.go @@ -92,7 +92,7 @@ type Server struct { // Keystore seals NTS cookies returned to clients. Keystore Keystore // Cookies is the number of NewCookie records to issue per exchange. - // Defaults to 8 when unset and is capped at maxCookies (32); larger values + // Defaults to 8 when unset and is capped at 32; larger values // are clamped to bound per-connection cost. Cookies uint16 // SupportedAEAD is the list of AEAD algorithm IDs the server will negotiate. diff --git a/ntp/ntske/server_test.go b/ntp/ntske/server_test.go index 76d2b985..55540995 100644 --- a/ntp/ntske/server_test.go +++ b/ntp/ntske/server_test.go @@ -198,10 +198,10 @@ func TestServerDefaultCookieCount(t *testing.T) { NewAEADAlgorithm(gcmSIV), NewEndOfMessage(), }) - require.Len(t, recordsByType(resp)[RecordNewCookie], defaultCookies) + require.Len(t, recordsByType(resp)[RecordNewCookie], 8) } -// TestServerClampsCookieCount checks that a Cookies value above the maxCookies +// TestServerClampsCookieCount checks that a Cookies value above the 32-cookie // upper bound is clamped, so a large or misconfigured value cannot be turned // into a per-handshake cookie-sealing / response-size amplification vector. func TestServerClampsCookieCount(t *testing.T) { @@ -215,7 +215,7 @@ func TestServerClampsCookieCount(t *testing.T) { NewAEADAlgorithm(gcmSIV), NewEndOfMessage(), }) - require.Len(t, recordsByType(resp)[RecordNewCookie], maxCookies) + require.Len(t, recordsByType(resp)[RecordNewCookie], 32) } // TestServerAEADPreferenceOrder verifies that when the client lists several