Skip to content

Commit 18bd5aa

Browse files
committed
MDEV-40445 TLS 1.3 early data
1 parent c7bf3c5 commit 18bd5aa

7 files changed

Lines changed: 154 additions & 79 deletions

File tree

include/ma_tls.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ typedef struct st_ma_pvio_tls {
4343
MARIADB_PVIO *pvio;
4444
void *ssl;
4545
MARIADB_X509_INFO cert_info;
46+
const uchar *early_data; /* TLS 1.3 early data */
47+
size_t early_data_len;
4648
} MARIADB_TLS;
4749

4850
/* Function prototypes */
@@ -186,7 +188,8 @@ my_bool ma_pvio_tls_close(MARIADB_TLS *ctls);
186188
int ma_pvio_tls_verify_server_cert(MARIADB_TLS *ctls, unsigned int flags);
187189
const char *ma_pvio_tls_cipher(MARIADB_TLS *ctls);
188190
my_bool ma_pvio_tls_check_fp(MARIADB_TLS *ctls, const char *fp, const char *fp_list);
189-
my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio);
191+
my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio, const uchar *early_data,
192+
size_t early_data_len);
190193
void ma_pvio_tls_set_connection(MYSQL *mysql);
191194
void ma_pvio_tls_end();
192195
unsigned int ma_pvio_tls_get_peer_cert_info(MARIADB_TLS *ctls, unsigned int size);
@@ -219,4 +222,9 @@ int ma_tls_session_received(MARIADB_TLS *ctls, SSL_SESSION *session,
219222
void ma_pvio_cache_tls_session(MYSQL *mysql);
220223

221224

225+
/* Whether the early data was sent and accepted. When it was not -
226+
the caller must send the packet again the ordinary way. */
227+
my_bool ma_tls_early_data_accepted(MARIADB_TLS *ctls);
228+
229+
222230
#endif /* _ma_tls_h_ */

libmariadb/ma_pvio.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,11 @@ my_bool ma_pvio_has_data(MARIADB_PVIO *pvio, ssize_t *data_len)
401401
#ifdef HAVE_TLS
402402

403403
/* {{{ my_bool ma_pvio_start_ssl */
404-
my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio)
404+
/*
405+
Start TLS handshake, optionally sending TLS 1.3 early data
406+
*/
407+
my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio, const uchar *early_data,
408+
size_t early_data_len)
405409
{
406410
if (!pvio || !pvio->mysql)
407411
return 1;
@@ -410,6 +414,8 @@ my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio)
410414
{
411415
return 1;
412416
}
417+
pvio->ctls->early_data= early_data;
418+
pvio->ctls->early_data_len= early_data_len;
413419
if (ma_pvio_tls_connect(pvio->ctls))
414420
{
415421
free(pvio->ctls);

libmariadb/ma_tls.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,9 @@ int ma_tls_session_received(MARIADB_TLS *ctls, SSL_SESSION *session,
308308
memcpy(entry->key, rs->key, MA_SHA256_HASH_SIZE);
309309

310310
rs->session[rs->session_count++]= entry;
311+
312+
if (ctls->early_data) /* in this case the session comes after auth: safe to cache */
313+
ma_pvio_cache_tls_session(ctls->pvio->mysql);
311314
return 0;
312315
}
313316

libmariadb/secure/gnutls.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,12 @@ void ma_tls_session_free(SSL_SESSION *session)
11441144
free(session);
11451145
}
11461146

1147+
/* 0-RTT is not implemented for GnuTLS yet */
1148+
my_bool ma_tls_early_data_accepted(MARIADB_TLS *ctls __attribute__((unused)))
1149+
{
1150+
return 0;
1151+
}
1152+
11471153
static void ma_tls_session_keep(MARIADB_TLS *ctls, gnutls_session_t ssl)
11481154
{
11491155
SSL_SESSION *session;

libmariadb/secure/openssl.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,8 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
742742

743743
if ((session= ma_tls_session_cache_get(ctls)))
744744
SSL_set_session(ssl, session);
745+
else
746+
ctls->early_data= NULL; /* nothing to resume, so no early data either */
745747

746748
/* Route all TLS I/O through the pvio read/write methods (and thus through
747749
the always non-blocking socket with poll()/select() based timeouts)
@@ -757,6 +759,19 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
757759
/* CONC-732: Always set verification callback to avoid OpenSSL output */
758760
SSL_set_verify(ssl, SSL_VERIFY_PEER, ma_verification_callback);
759761

762+
#ifdef TLS1_3_VERSION
763+
if (ctls->early_data)
764+
{
765+
size_t written;
766+
if (SSL_SESSION_get_max_early_data(session) < ctls->early_data_len ||
767+
!SSL_write_early_data(ssl, ctls->early_data, ctls->early_data_len,
768+
&written))
769+
ctls->early_data= NULL;
770+
}
771+
#else
772+
ctls->early_data= NULL;
773+
#endif
774+
760775
/* The BIO blocks (poll() in sync, fiber yield in async), so SSL_connect()
761776
normally completes in one shot. The loop is kept defensive: should the
762777
BIO ever report WANT_READ/WANT_WRITE, wait the requested direction
@@ -801,6 +816,17 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
801816
return 0;
802817
}
803818

819+
820+
my_bool ma_tls_early_data_accepted(MARIADB_TLS *ctls)
821+
{
822+
#ifdef TLS1_3_VERSION
823+
return ctls->early_data &&
824+
SSL_get_early_data_status((SSL *)ctls->ssl) == SSL_EARLY_DATA_ACCEPTED;
825+
#else
826+
return 0;
827+
#endif
828+
}
829+
804830
ssize_t ma_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length)
805831
{
806832
/* The custom BIO blocks (via poll()) in synchronous mode and performs

libmariadb/secure/schannel.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,12 @@ void ma_tls_session_free(MA_SSL_SESSION *session __attribute__((unused)))
250250
{
251251
}
252252

253+
/* 0-RTT is not implemented for Schannel yet */
254+
my_bool ma_tls_early_data_accepted(MARIADB_TLS *ctls __attribute__((unused)))
255+
{
256+
return 0;
257+
}
258+
253259
/* {{{ void *ma_tls_init(MARIADB_TLS *ctls, MYSQL *mysql) */
254260
void *ma_tls_init(MYSQL *mysql, MARIADB_TLS *ctls __attribute__((unused)))
255261
{

plugins/auth/my_auth.c

Lines changed: 97 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,9 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
287287
{
288288
MYSQL *mysql= mpvio->mysql;
289289
NET *net= &mysql->net;
290-
char *buff, *end;
291-
size_t conn_attr_len= (mysql->options.extension) ?
290+
char *buff, *end, *pre_ssl_end, *early_data= NULL;
291+
size_t early_data_len;
292+
size_t conn_attr_len= (mysql->options.extension) ?
292293
mysql->options.extension->connect_attrs_len : 0;
293294
size_t proxy_header_len= 0;
294295
char *proxy_header=
@@ -415,6 +416,73 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
415416
int3store(buff+2, net->max_packet_size);
416417
end= buff+5;
417418
}
419+
/* The rest of the packet is built before the TLS handshake starts,
420+
so that it can be sent as early data in ma_pvio_start_ssl() */
421+
pre_ssl_end= end;
422+
423+
if (mysql->user && mysql->user[0])
424+
ma_strmake(end, mysql->user, USERNAME_LENGTH);
425+
else
426+
read_user_name(end);
427+
428+
/* We have to handle different version of handshake here */
429+
end+= strlen(end) + 1;
430+
if (data_len)
431+
{
432+
if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION)
433+
{
434+
if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA)
435+
{
436+
end= (char *)mysql_net_store_length((uchar *)end, data_len);
437+
}
438+
else {
439+
/* Without CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA capability password
440+
length is limited up to 255 chars */
441+
if (data_len > 0xFF)
442+
goto error;
443+
*end++= data_len;
444+
}
445+
memcpy(end, data, data_len);
446+
end+= data_len;
447+
}
448+
else
449+
{
450+
DBUG_ASSERT(data_len == SCRAMBLE_LENGTH_323 + 1); /* incl. \0 at the end */
451+
memcpy(end, data, data_len);
452+
end+= data_len;
453+
}
454+
}
455+
else
456+
*end++= 0;
457+
458+
/* Add database if needed */
459+
if (mpvio->db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB))
460+
{
461+
end= ma_strmake(end, mpvio->db, NAME_LEN) + 1;
462+
mysql->db= strdup(mpvio->db);
463+
}
464+
465+
if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH)
466+
end= ma_strmake(end, mpvio->plugin->name, NAME_LEN) + 1;
467+
468+
end= ma_send_connect_attr(mysql, (unsigned char *)end);
469+
470+
/* MySQL 8.0:
471+
If zstd compresson was specified, the server expects
472+
1 byte for compression level
473+
*/
474+
if (mysql->client_flag & CLIENT_ZSTD_COMPRESSION)
475+
{
476+
uchar compression_level= 3;
477+
if (mysql->options.extension &&
478+
mysql->options.extension->zstd_compression_level >= 1 &&
479+
mysql->options.extension->zstd_compression_level <= 20)
480+
{
481+
compression_level= mysql->options.extension->zstd_compression_level;
482+
}
483+
*end++= compression_level;
484+
}
485+
418486
#ifdef HAVE_TLS
419487
if (mysql->options.ssl_key ||
420488
mysql->options.ssl_cert ||
@@ -428,8 +496,7 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
428496
#endif
429497
)
430498
mysql->options.use_ssl= 1;
431-
if (mysql->options.use_ssl &&
432-
(mysql->client_flag & CLIENT_SSL))
499+
if (mysql->options.use_ssl && (mysql->client_flag & CLIENT_SSL))
433500
{
434501
unsigned int verify_flags= 0;
435502
/*
@@ -444,7 +511,8 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
444511
proxy_header= NULL;
445512
}
446513

447-
if (ma_net_write(net, (unsigned char *)buff, (size_t) (end-buff)) || ma_net_flush(net))
514+
if (ma_net_write(net, (uchar *)buff, (size_t) (pre_ssl_end - buff))
515+
|| ma_net_flush(net))
448516
{
449517
my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
450518
ER(CR_SERVER_LOST_EXTENDED),
@@ -453,7 +521,16 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
453521
goto error;
454522
}
455523
mysql->net.tls_verify_status = 0;
456-
if (ma_pvio_start_ssl(mysql->net.pvio))
524+
525+
/* Early data aren't fully protected by TLS, so only used with
526+
password_and_hashing() plugins. */
527+
if (password_and_hashing(mysql, mpvio->plugin))
528+
{
529+
early_data= buff;
530+
early_data_len= end - buff;
531+
}
532+
533+
if (ma_pvio_start_ssl(mysql->net.pvio, (uchar*)early_data, early_data_len))
457534
goto error;
458535

459536
verify_flags= MARIADB_TLS_VERIFY_PERIOD;
@@ -487,83 +564,26 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
487564
if (!password_and_hashing(mysql, mpvio->plugin))
488565
goto error;
489566
}
567+
/* Need to resent if early data wasn't accepted */
568+
if (!ma_tls_early_data_accepted(mysql->net.pvio->ctls))
569+
early_data= NULL;
490570
}
491571
#endif /* HAVE_TLS */
492572

493-
/* This needs to be changed as it's not useful with big packets */
494-
if (mysql->user && mysql->user[0])
495-
ma_strmake(end, mysql->user, USERNAME_LENGTH);
496-
else
497-
read_user_name(end);
498-
499-
/* We have to handle different version of handshake here */
500-
end+= strlen(end) + 1;
501-
if (data_len)
502-
{
503-
if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION)
504-
{
505-
if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA)
506-
{
507-
end= (char *)mysql_net_store_length((uchar *)end, data_len);
508-
}
509-
else {
510-
/* Without CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA capability password
511-
length is limited up to 255 chars */
512-
if (data_len > 0xFF)
513-
goto error;
514-
*end++= data_len;
515-
}
516-
memcpy(end, data, data_len);
517-
end+= data_len;
518-
}
519-
else
520-
{
521-
DBUG_ASSERT(data_len == SCRAMBLE_LENGTH_323 + 1); /* incl. \0 at the end */
522-
memcpy(end, data, data_len);
523-
end+= data_len;
524-
}
525-
}
526-
else
527-
*end++= 0;
528-
529-
/* Add database if needed */
530-
if (mpvio->db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB))
531-
{
532-
end= ma_strmake(end, mpvio->db, NAME_LEN) + 1;
533-
mysql->db= strdup(mpvio->db);
534-
}
535-
536-
if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH)
537-
end= ma_strmake(end, mpvio->plugin->name, NAME_LEN) + 1;
538-
539-
end= ma_send_connect_attr(mysql, (unsigned char *)end);
540-
541-
/* MySQL 8.0:
542-
If zstd compresson was specified, the server expects
543-
1 byte for compression level
544-
*/
545-
if (mysql->client_flag & CLIENT_ZSTD_COMPRESSION)
573+
if (!early_data)
546574
{
547-
uchar compression_level= 3;
548-
if (mysql->options.extension &&
549-
mysql->options.extension->zstd_compression_level >= 1 &&
550-
mysql->options.extension->zstd_compression_level <= 20)
575+
if (proxy_header_len)
576+
ma_net_write_buff(net, proxy_header, proxy_header_len);
577+
/* Write authentication package */
578+
if (ma_net_write(net, (uchar *)buff, (size_t) (end - buff))
579+
|| ma_net_flush(net))
551580
{
552-
compression_level= mysql->options.extension->zstd_compression_level;
581+
my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
582+
ER(CR_SERVER_LOST_EXTENDED),
583+
"sending authentication information",
584+
errno);
585+
goto error;
553586
}
554-
*end++= compression_level;
555-
}
556-
557-
if (proxy_header_len)
558-
ma_net_write_buff(net, proxy_header, proxy_header_len);
559-
/* Write authentication package */
560-
if (ma_net_write(net, (unsigned char *)buff, (size_t) (end-buff)) || ma_net_flush(net))
561-
{
562-
my_set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN,
563-
ER(CR_SERVER_LOST_EXTENDED),
564-
"sending authentication information",
565-
errno);
566-
goto error;
567587
}
568588
free(buff);
569589
return 0;

0 commit comments

Comments
 (0)