Skip to content

Commit 672da2a

Browse files
authored
Merge pull request #10633 from holtrop-wolfssl/f-4141
Add unit tests that TLS resumption fails due to ALPN mismatch
2 parents f3ab345 + b01382c commit 672da2a

2 files changed

Lines changed: 157 additions & 0 deletions

File tree

tests/api/test_tls.c

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,159 @@ int test_tls13_resumption_with_alpn(void)
12441244
return EXPECT_RESULT();
12451245
}
12461246

1247+
/* TLS 1.2 stateful (session-ID) resumption must fall back to a full
1248+
* handshake if the ALPN protocol negotiated for the resumed connection
1249+
* does not match the ALPN bound to the original session. Mirrors
1250+
* test_tls12_session_id_resumption_sni_mismatch but varies ALPN instead
1251+
* of SNI. */
1252+
int test_tls12_session_id_resumption_alpn_mismatch(void)
1253+
{
1254+
EXPECT_DECLS;
1255+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
1256+
!defined(WOLFSSL_NO_TLS12) && defined(HAVE_ALPN) && \
1257+
defined(HAVE_SESSION_TICKET) && !defined(NO_SESSION_CACHE)
1258+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
1259+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
1260+
WOLFSSL_SESSION *sess = NULL;
1261+
struct test_memio_ctx test_ctx;
1262+
const char alpnA[] = "h2";
1263+
const char alpnB[] = "http/1.1";
1264+
1265+
/* Step 1: full TLS 1.2 handshake negotiating ALPN=h2, with the
1266+
* session ticket path disabled so resumption can only happen via the
1267+
* server's session-ID cache. The negotiated ALPN is retained on
1268+
* ssl->extensions by ALPN_Select, so SetupSession binds its hash to
1269+
* the cached session. */
1270+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
1271+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
1272+
wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0);
1273+
ExpectIntEQ(wolfSSL_NoTicketTLSv12(ssl_c), WOLFSSL_SUCCESS);
1274+
ExpectIntEQ(wolfSSL_NoTicketTLSv12(ssl_s), WOLFSSL_SUCCESS);
1275+
ExpectIntEQ(wolfSSL_UseALPN(ssl_c, (char*)alpnA, (word32)XSTRLEN(alpnA),
1276+
WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS);
1277+
ExpectIntEQ(wolfSSL_UseALPN(ssl_s, (char*)alpnA, (word32)XSTRLEN(alpnA),
1278+
WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS);
1279+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
1280+
/* Sanity: the first handshake was not a resumption. */
1281+
ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0);
1282+
ExpectNotNull(sess = wolfSSL_get1_session(ssl_c));
1283+
1284+
wolfSSL_free(ssl_c); ssl_c = NULL;
1285+
wolfSSL_free(ssl_s); ssl_s = NULL;
1286+
1287+
/* Step 2: new SSL objects on the SAME WOLFSSL_CTX (so the server's
1288+
* session cache still holds the entry from step 1). The client offers
1289+
* the saved session but both sides now advertise a *different* ALPN
1290+
* (http/1.1), so the handshake negotiates http/1.1. The server's cache
1291+
* lookup matches by session ID, but the server MUST NOT resume because
1292+
* the negotiated ALPN differs from the one bound to the original
1293+
* session. */
1294+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
1295+
ExpectNotNull(ssl_c = wolfSSL_new(ctx_c));
1296+
wolfSSL_SetIOReadCtx(ssl_c, &test_ctx);
1297+
wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx);
1298+
ExpectNotNull(ssl_s = wolfSSL_new(ctx_s));
1299+
wolfSSL_SetIOReadCtx(ssl_s, &test_ctx);
1300+
wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx);
1301+
ExpectIntEQ(wolfSSL_NoTicketTLSv12(ssl_c), WOLFSSL_SUCCESS);
1302+
ExpectIntEQ(wolfSSL_NoTicketTLSv12(ssl_s), WOLFSSL_SUCCESS);
1303+
ExpectIntEQ(wolfSSL_UseALPN(ssl_c, (char*)alpnB, (word32)XSTRLEN(alpnB),
1304+
WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS);
1305+
ExpectIntEQ(wolfSSL_UseALPN(ssl_s, (char*)alpnB, (word32)XSTRLEN(alpnB),
1306+
WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS);
1307+
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS);
1308+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
1309+
1310+
/* Expected behavior: server falls back to a full handshake because the
1311+
* negotiated ALPN does not match the ALPN bound to the cached session.
1312+
* Both sides should report no resumption. */
1313+
ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0);
1314+
ExpectIntEQ(wolfSSL_session_reused(ssl_c), 0);
1315+
1316+
wolfSSL_SESSION_free(sess);
1317+
wolfSSL_free(ssl_c);
1318+
wolfSSL_free(ssl_s);
1319+
wolfSSL_CTX_free(ctx_c);
1320+
wolfSSL_CTX_free(ctx_s);
1321+
#endif
1322+
return EXPECT_RESULT();
1323+
}
1324+
1325+
/* TLS 1.3 PSK resumption must fall back to a full handshake if the ALPN
1326+
* protocol negotiated for the resumed connection does not match the ALPN
1327+
* bound to the original session. Mirrors
1328+
* test_tls13_session_resumption_sni_mismatch but varies ALPN instead of
1329+
* SNI. */
1330+
int test_tls13_session_resumption_alpn_mismatch(void)
1331+
{
1332+
EXPECT_DECLS;
1333+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \
1334+
defined(HAVE_ALPN) && defined(HAVE_SESSION_TICKET) && \
1335+
!defined(NO_SESSION_CACHE)
1336+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
1337+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
1338+
WOLFSSL_SESSION *sess = NULL;
1339+
struct test_memio_ctx test_ctx;
1340+
const char alpnA[] = "h2";
1341+
const char alpnB[] = "http/1.1";
1342+
byte readBuf[16];
1343+
1344+
/* Step 1: full TLS 1.3 handshake negotiating ALPN=h2 to obtain a
1345+
* session ticket. The negotiated ALPN is retained on ssl->extensions
1346+
* by ALPN_Select and bound to the ticket. */
1347+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
1348+
ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
1349+
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
1350+
ExpectIntEQ(wolfSSL_UseALPN(ssl_c, (char*)alpnA, (word32)XSTRLEN(alpnA),
1351+
WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS);
1352+
ExpectIntEQ(wolfSSL_UseALPN(ssl_s, (char*)alpnA, (word32)XSTRLEN(alpnA),
1353+
WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS);
1354+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
1355+
/* Sanity: the first handshake was not a resumption. */
1356+
ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0);
1357+
/* Drive the post-handshake NewSessionTicket through to the client so
1358+
* the saved session is a real resumption ticket. */
1359+
ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1);
1360+
ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ);
1361+
ExpectNotNull(sess = wolfSSL_get1_session(ssl_c));
1362+
1363+
wolfSSL_free(ssl_c); ssl_c = NULL;
1364+
wolfSSL_free(ssl_s); ssl_s = NULL;
1365+
1366+
/* Step 2: new SSL objects on the SAME WOLFSSL_CTX (so the server's
1367+
* ticket key still matches). The client offers the saved session but
1368+
* both sides now advertise a *different* ALPN (http/1.1). The server
1369+
* MUST NOT resume because the negotiated ALPN differs from the one
1370+
* bound to the original ticket. */
1371+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
1372+
ExpectNotNull(ssl_c = wolfSSL_new(ctx_c));
1373+
wolfSSL_SetIOReadCtx(ssl_c, &test_ctx);
1374+
wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx);
1375+
ExpectNotNull(ssl_s = wolfSSL_new(ctx_s));
1376+
wolfSSL_SetIOReadCtx(ssl_s, &test_ctx);
1377+
wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx);
1378+
ExpectIntEQ(wolfSSL_UseALPN(ssl_c, (char*)alpnB, (word32)XSTRLEN(alpnB),
1379+
WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS);
1380+
ExpectIntEQ(wolfSSL_UseALPN(ssl_s, (char*)alpnB, (word32)XSTRLEN(alpnB),
1381+
WOLFSSL_ALPN_FAILED_ON_MISMATCH), WOLFSSL_SUCCESS);
1382+
ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS);
1383+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
1384+
1385+
/* Expected behavior: server falls back to a full handshake because the
1386+
* negotiated ALPN does not match the ALPN bound to the cached ticket.
1387+
* Both sides should report no resumption. */
1388+
ExpectIntEQ(wolfSSL_session_reused(ssl_s), 0);
1389+
ExpectIntEQ(wolfSSL_session_reused(ssl_c), 0);
1390+
1391+
wolfSSL_SESSION_free(sess);
1392+
wolfSSL_free(ssl_c);
1393+
wolfSSL_free(ssl_s);
1394+
wolfSSL_CTX_free(ctx_c);
1395+
wolfSSL_CTX_free(ctx_s);
1396+
#endif
1397+
return EXPECT_RESULT();
1398+
}
1399+
12471400
int test_tls_set_curves_list_ecc_fallback(void)
12481401
{
12491402
EXPECT_DECLS;

tests/api/test_tls.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ int test_tls_set_session_min_downgrade(void);
3737
int test_tls12_session_id_resumption_sni_mismatch(void);
3838
int test_tls13_session_resumption_sni_mismatch(void);
3939
int test_tls13_resumption_with_alpn(void);
40+
int test_tls12_session_id_resumption_alpn_mismatch(void);
41+
int test_tls13_session_resumption_alpn_mismatch(void);
4042
int test_tls_set_curves_list_ecc_fallback(void);
4143
int test_tls12_corrupted_finished(void);
4244
int test_tls12_peerauth_failsafe(void);
@@ -63,6 +65,8 @@ int test_record_size_cache_invalidated_on_renegotiation(void);
6365
TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_sni_mismatch), \
6466
TEST_DECL_GROUP("tls", test_tls13_session_resumption_sni_mismatch), \
6567
TEST_DECL_GROUP("tls", test_tls13_resumption_with_alpn), \
68+
TEST_DECL_GROUP("tls", test_tls12_session_id_resumption_alpn_mismatch),\
69+
TEST_DECL_GROUP("tls", test_tls13_session_resumption_alpn_mismatch), \
6670
TEST_DECL_GROUP("tls", test_tls_set_curves_list_ecc_fallback), \
6771
TEST_DECL_GROUP("tls", test_tls12_corrupted_finished), \
6872
TEST_DECL_GROUP("tls", test_tls12_peerauth_failsafe), \

0 commit comments

Comments
 (0)