1+ // ============================================================================
2+ // API Surface + TLS 1.2 Session Ticket Tests
3+ // ============================================================================
4+ // Unit coverage for previously-unexercised public APIs (handshake_params_init,
5+ // ecp curve selection, ssl_read_new_session_ticket guards) plus a live TLS 1.2
6+ // session-ticket exchange against Python/OpenSSL.
7+ // Ports used: 20130.
8+ // ============================================================================
9+
10+ using namespace tls
11+ using std:: string_view
12+
13+ // ─── handshake_params_init zeroes all sensitive buffers ─────────────────────
14+ @ test
15+ public func API_handshake_params_init_zeroes (env : &mut TestEnv) {
16+ var hs_mem = malloc (sizeof (HandshakeParams)) as *mut HandshakeParams
17+ if (hs_mem == null) { env.error ("alloc failed" ); return }
18+
19+ // Poison the memory first so init provably writes the fields it owns.
20+ memset (hs_mem as *mut void, 0xAB , sizeof (HandshakeParams))
21+
22+ handshake_params_init (hs_mem)
23+
24+ var i : size_t = 0
25+ while (i < 64 ) {
26+ if (hs_mem.randbytes[i] != 0 ) { env.error ("randbytes must be zeroed" ); return }
27+ i += 1
28+ }
29+ i = 0
30+ while (i < 256 ) {
31+ if (hs_mem.premaster[i] != 0 ) { env.error ("premaster must be zeroed" ); return }
32+ i += 1
33+ }
34+ if (hs_mem.premaster_len != 0 ) { env.error ("premaster_len must start at 0" ) }
35+ if (hs_mem.psk_len != 0 ) { env.error ("psk_len must start at 0" ) }
36+ if (hs_mem.hello_retry_requested) { env.error ("hello_retry_requested must start false" ) }
37+ if (hs_mem.psk_accepted) { env.error ("psk_accepted must start false" ) }
38+
39+ unsafe { dealloc hs_mem }
40+ }
41+
42+ // ─── ecp_select_curve switches between P-256 and P-384 domain parameters ────
43+ @ test
44+ public func API_ecp_curve_selection_params (env : &mut TestEnv) {
45+ // P-256 (GLOBAL_CURVE == 0)
46+ ecp_select_curve (0 )
47+ if (ecp_curve_id () != 0 ) { env.error ("curve id should be 0 for P-256" ); return }
48+
49+ unsafe var p : Mpi; unsafe var n : Mpi; unsafe var gx : Mpi; unsafe var gy : Mpi; unsafe var b : Mpi
50+ ecp_curve_p (&raw mut p); ecp_curve_n (&raw mut n)
51+ ecp_curve_gx (&raw mut gx); ecp_curve_gy (&raw mut gy); ecp_curve_b (&raw mut b)
52+
53+ // P-256 prime: 2^256 - 2^224 + 2^192 + 2^96 - 1 → 256 bits
54+ if (mpi_bitlen (&raw mut p) != 256 ) { env.error ("P-256 p bitlen should be 256" ); return }
55+ // Group order n is also 256 bits
56+ if (mpi_bitlen (&raw mut n) != 256 ) { env.error ("P-256 n bitlen should be 256" ); return }
57+
58+ // Generator coordinates are canonical 256-bit values (nonzero, < p).
59+ if (mpi_cmp (&raw mut gx, &raw mut p) >= 0 ) { env.error ("P-256 Gx must be < p" ); return }
60+ if (mpi_is_zero (&raw mut gx)) { env.error ("P-256 Gx must not be zero" ); return }
61+ if (mpi_cmp (&raw mut gy, &raw mut p) >= 0 ) { env.error ("P-256 Gy must be < p" ); return }
62+
63+ // Curve equation check: y^2 mod p == (x^3 + a*x + b) mod p with a = p-3.
64+ unsafe var y2 : Mpi; unsafe var x3 : Mpi; unsafe var ax : Mpi; unsafe var a : Mpi
65+ unsafe var rhs : Mpi
66+ unsafe var three : Mpi
67+ mpi_lset (&raw mut three, 3 )
68+ mpi_sub (&raw mut a, &raw mut p, &raw mut three)
69+ mpi_mul (&raw mut y2, &raw mut gy, &raw mut gy)
70+ mpi_mod (&raw mut y2, &raw mut y2, &raw mut p)
71+ mpi_mul (&raw mut x3, &raw mut gx, &raw mut gx)
72+ mpi_mul (&raw mut x3, &raw mut x3, &raw mut gx)
73+ mpi_mul (&raw mut ax, &raw mut a, &raw mut gx)
74+ mpi_add (&raw mut rhs, &raw mut x3, &raw mut ax)
75+ mpi_add (&raw mut rhs, &raw mut rhs, &raw mut b)
76+ mpi_mod (&raw mut rhs, &raw mut rhs, &raw mut p)
77+ if (mpi_cmp (&raw mut y2, &raw mut rhs) != 0 ) {
78+ env.error ("P-256 generator must satisfy the curve equation" )
79+ return
80+ }
81+
82+ // P-384 (GLOBAL_CURVE == 1)
83+ ecp_select_curve (1 )
84+ if (ecp_curve_id () != 1 ) { env.error ("curve id should be 1 for P-384" ); return }
85+
86+ unsafe var p384 : Mpi; unsafe var gx384 : Mpi; unsafe var b384 : Mpi
87+ ecp_curve_p (&raw mut p384)
88+ ecp_curve_gx (&raw mut gx384)
89+ ecp_curve_b (&raw mut b384)
90+
91+ // P-384 prime: 2^384 - 2^128 - 2^96 + 2^32 - 1 → 384 bits
92+ if (mpi_bitlen (&raw mut p384) != 384 ) { env.error ("P-384 p bitlen should be 384" ); return }
93+ if (mpi_bitlen (&raw mut gx384) == 0 ) { env.error ("P-384 Gx must be nonzero" ); return }
94+ if (mpi_cmp (&raw mut gx384, &raw mut p384) >= 0 ) { env.error ("P-384 Gx must be < p" ); return }
95+
96+ // Restore the default so any later logic relying on global state is sane.
97+ ecp_select_curve (0 )
98+ }
99+
100+ // ─── ssl_read_new_session_ticket without a transport fails cleanly ──────────
101+ @ test
102+ public func API_ssl_read_new_session_ticket_no_socket_fails (env : &mut TestEnv) {
103+ unsafe var ctx : SSLContext; ssl_init (&raw mut ctx)
104+
105+ var ret = ssl_read_new_session_ticket (&raw mut ctx)
106+ if (ret >= 0 ) {
107+ env.error ("reading a session ticket with no transport must fail" )
108+ }
109+ ssl_free (&raw mut ctx)
110+ }
111+
112+ // ─── TLS 1.2 NewSessionTicket: received, decrypted and stored post-handshake
113+ // OpenSSL servers send an encrypted NewSessionTicket right after ServerFinished
114+ // in TLS 1.2. The client must decrypt it through the active transform, store
115+ // the ticket + master secret in ssl.session, and keep the connection usable.
116+ @ test
117+ @ test.timeout (60000 )
118+ public func E2E_tls12_session_ticket_received_and_stored (env : &mut TestEnv) {
119+ write_tls_python_utils ()
120+ test_kill_port (20130u )
121+ test_server_wait ()
122+ test_py_run_foreground (string_view ("cert /tmp/tls_20130_cert.pem /tmp/tls_20130_key.pem localhost rsa" ))
123+ // OpenSSL 3 disables static-RSA suites at the default security level; pin
124+ // them server-side so the TLS 1.2 RSA key-exchange path is negotiated.
125+ // srv handles exactly one exchange (recv → OK), which is all we need here.
126+ test_py_run_background (string_view ("srv /tmp/tls_20130_cert.pem /tmp/tls_20130_key.pem 20130 1.2 AES128-GCM-SHA256:@SECLEVEL=0" ))
127+ test_server_wait ()
128+
129+ unsafe var ctx : SSLContext; ssl_init (&raw mut ctx)
130+ var config = ssl_config_init (SSL_IS_CLIENT)
131+ config.authmode = SSL_VERIFY_NONE
132+ config.max_tls_version = SSL_VERSION_TLS1_2
133+ config.ciphersuite_list[0 ] = TLS_RSA_WITH_AES_128_GCM_SHA256 as u16
134+ config.ciphersuite_count = 1
135+ ssl_set_config (&raw mut ctx, &raw mut config)
136+
137+ var ret = tls_connect (&raw mut ctx, "127.0.0.1" , 20130u )
138+ if (ret < 0 ) {
139+ env.error ("TLS12 ticket: connect failed" )
140+ ssl_free (&raw mut ctx)
141+ test_kill_port (20130u )
142+ return
143+ }
144+
145+ if (ctx.session == null) { env.error ("TLS12 ticket: session not allocated" ); ssl_free (&raw mut ctx); test_kill_port (20130u ); return }
146+
147+ // The server's NewSessionTicket arrives before its app-data response;
148+ // pull it explicitly through the dedicated post-handshake API.
149+ ret = ssl_read_new_session_ticket (&raw mut ctx)
150+ if (ret != 0 ) {
151+ // Legitimate gap: record what happened but do not mask it as success.
152+ env.error ("TLS12 ticket: ssl_read_new_session_ticket did not accept the NST" )
153+ } else {
154+ if (ctx.session.ticket == null || ctx.session.ticket_len == 0 ) {
155+ env.error ("TLS12 ticket: ticket was not stored after processing NST" )
156+ }
157+ var ms_nonzero = false
158+ var mi : size_t = 0
159+ while (mi < 48 ) {
160+ if (ctx.session.master[mi] != 0 ) { ms_nonzero = true }
161+ mi += 1
162+ }
163+ if (! ms_nonzero) { env.error ("TLS12 ticket: master secret missing from session" ) }
164+ }
165+
166+ // The connection must remain fully usable afterwards.
167+ var ping = "t\0 " as *char
168+ ssl_write (&raw mut ctx, ping as *u8, 1 )
169+ unsafe var buf : [64 ]u8
170+ var n = ssl_read (&raw mut ctx, &raw mut buf[0 ], 64 )
171+ if (n != 2 || buf[0 ] != 79 || buf[1 ] != 75 ) {
172+ env.error ("TLS12 ticket: connection unusable after reading NST" )
173+ }
174+
175+ ssl_close_notify (&raw mut ctx)
176+ ssl_free (&raw mut ctx)
177+ test_kill_port (20130u )
178+ }
0 commit comments