Skip to content

Commit 8885e59

Browse files
committed
TLS 1.3, plaintext alert: ignore when expecting encrypted
In TLS 1.3, ignore valid unencrypted alerts that appear after encryption has started. Only ignore WOLFSSL_ALERT_COUNT_MAX-1 alerts.
1 parent b42e9a9 commit 8885e59

File tree

5 files changed

+295
-48
lines changed

5 files changed

+295
-48
lines changed

.github/workflows/os-check.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ jobs:
6363
'--enable-coding=no',
6464
'--enable-dtls --enable-dtls13 --enable-ocspstapling --enable-ocspstapling2
6565
--enable-cert-setup-cb --enable-sessioncerts',
66+
'--enable-dtls --enable-dtls13 --enable-tls13
67+
CPPFLAGS=-DWOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC',
6668
'--disable-sni --disable-ecc --disable-tls13 --disable-secure-renegotiation-info',
6769
'CPPFLAGS=-DWOLFSSL_BLIND_PRIVATE_KEY',
6870
'--enable-all --enable-certgencache',

.wolfssl_known_macro_extras

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,7 @@ WOLFSSL_TICKET_ENC_HMAC_SHA512
900900
WOLFSSL_TI_CURRTIME
901901
WOLFSSL_TLS13_DRAFT
902902
WOLFSSL_TLS13_IGNORE_AEAD_LIMITS
903+
WOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC
903904
WOLFSSL_TLS13_SHA512
904905
WOLFSSL_TLS13_TICKET_BEFORE_FINISHED
905906
WOLFSSL_TLSX_PQC_MLKEM_STORE_PRIV_KEY

src/internal.c

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21685,20 +21685,20 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
2168521685
byte code;
2168621686
word32 dataSz = (word32)ssl->curSize;
2168721687

21688-
#if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA)
21689-
if (ssl->hsInfoOn)
21690-
AddPacketName(ssl, "Alert");
21691-
if (ssl->toInfoOn) {
21692-
/* add record header back on to info + alert bytes level/code */
21693-
int ret = AddPacketInfo(ssl, "Alert", alert, input + *inOutIdx,
21694-
ALERT_SIZE, READ_PROTO, RECORD_HEADER_SZ, ssl->heap);
21695-
if (ret != 0)
21696-
return ret;
21697-
#ifdef WOLFSSL_CALLBACKS
21698-
AddLateRecordHeader(&ssl->curRL, &ssl->timeoutInfo);
21699-
#endif
21700-
}
21701-
#endif
21688+
#if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA)
21689+
if (ssl->hsInfoOn)
21690+
AddPacketName(ssl, "Alert");
21691+
if (ssl->toInfoOn) {
21692+
/* add record header back on to info + alert bytes level/code */
21693+
int ret = AddPacketInfo(ssl, "Alert", alert, input + *inOutIdx,
21694+
ALERT_SIZE, READ_PROTO, RECORD_HEADER_SZ, ssl->heap);
21695+
if (ret != 0)
21696+
return ret;
21697+
#ifdef WOLFSSL_CALLBACKS
21698+
AddLateRecordHeader(&ssl->curRL, &ssl->timeoutInfo);
21699+
#endif
21700+
}
21701+
#endif
2170221702

2170321703
if (IsEncryptionOn(ssl, 0))
2170421704
dataSz -= ssl->keys.padSz;
@@ -21713,11 +21713,18 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
2171321713

2171421714
level = input[(*inOutIdx)++];
2171521715
code = input[(*inOutIdx)++];
21716-
ssl->alert_history.last_rx.code = code;
21717-
ssl->alert_history.last_rx.level = level;
2171821716
*type = code;
21719-
if (level == alert_fatal) {
21720-
ssl->options.isClosed = 1; /* Don't send close_notify */
21717+
#ifdef WOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC
21718+
/* Don't process alert when TLS 1.3 and encrypting but plaintext alert. */
21719+
if (!IsAtLeastTLSv1_3(ssl->version) || !IsEncryptionOn(ssl, 0) ||
21720+
ssl->keys.decryptedCur)
21721+
#endif
21722+
{
21723+
ssl->alert_history.last_rx.code = code;
21724+
ssl->alert_history.last_rx.level = level;
21725+
if (level == alert_fatal) {
21726+
ssl->options.isClosed = 1; /* Don't send close_notify */
21727+
}
2172121728
}
2172221729

2172321730
if (++ssl->options.alertCount >= WOLFSSL_ALERT_COUNT_MAX) {
@@ -21731,20 +21738,35 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
2173121738
}
2173221739

2173321740
LogAlert(*type);
21734-
if (*type == close_notify) {
21735-
ssl->options.closeNotify = 1;
21741+
if (IsAtLeastTLSv1_3(ssl->version) && IsEncryptionOn(ssl, 0) &&
21742+
!ssl->keys.decryptedCur)
21743+
{
21744+
#ifdef WOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC
21745+
/* Ignore alert if TLS 1.3 and encrypting but was plaintext alert. */
21746+
*type = invalid_alert;
21747+
level = alert_none;
21748+
21749+
#else
21750+
/* Unexpected message when encryption is on and alert not encrypted. */
21751+
SendAlert(ssl, alert_fatal, unexpected_message);
21752+
WOLFSSL_ERROR_VERBOSE(PARSE_ERROR);
21753+
return PARSE_ERROR;
21754+
#endif
2173621755
}
2173721756
else {
21738-
/*
21739-
* A close_notify alert doesn't mean there's been an error, so we only
21740-
* add other types of alerts to the error queue
21741-
*/
21742-
WOLFSSL_ERROR(*type);
21757+
if (*type == close_notify) {
21758+
ssl->options.closeNotify = 1;
21759+
}
21760+
else {
21761+
/*
21762+
* A close_notify alert doesn't mean there's been an error, so we
21763+
* only add other types of alerts to the error queue
21764+
*/
21765+
WOLFSSL_ERROR(*type);
21766+
}
2174321767
}
21744-
21745-
if (IsEncryptionOn(ssl, 0)) {
21768+
if (IsEncryptionOn(ssl, 0))
2174621769
*inOutIdx += ssl->keys.padSz;
21747-
}
2174821770

2174921771
return level;
2175021772
}
@@ -22495,7 +22517,8 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr)
2249522517
#ifdef WOLFSSL_TLS13
2249622518
if (IsAtLeastTLSv1_3(ssl->version) && IsEncryptionOn(ssl, 0) &&
2249722519
ssl->curRL.type != application_data &&
22498-
ssl->curRL.type != change_cipher_spec) {
22520+
ssl->curRL.type != change_cipher_spec &&
22521+
ssl->curRL.type != alert) {
2249922522
SendAlert(ssl, alert_fatal, unexpected_message);
2250022523
WOLFSSL_ERROR_VERBOSE(PARSE_ERROR);
2250122524
return PARSE_ERROR;
@@ -22603,9 +22626,9 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr)
2260322626
case decryptMessage:
2260422627

2260522628
if (IsEncryptionOn(ssl, 0) && ssl->keys.decryptedCur == 0 &&
22606-
(!IsAtLeastTLSv1_3(ssl->version) ||
22607-
ssl->curRL.type != change_cipher_spec))
22608-
{
22629+
(!IsAtLeastTLSv1_3(ssl->version) ||
22630+
(ssl->curRL.type != change_cipher_spec &&
22631+
ssl->curRL.type != alert))) {
2260922632
ret = DoDecrypt(ssl);
2261022633
#ifdef WOLFSSL_ASYNC_CRYPT
2261122634
if (ret == WC_NO_ERR_TRACE(WC_PENDING_E))
@@ -22682,9 +22705,9 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr)
2268222705
case verifyMessage:
2268322706

2268422707
if (IsEncryptionOn(ssl, 0) && ssl->keys.decryptedCur == 0 &&
22685-
(!IsAtLeastTLSv1_3(ssl->version) ||
22686-
ssl->curRL.type != change_cipher_spec))
22687-
{
22708+
(!IsAtLeastTLSv1_3(ssl->version) ||
22709+
(ssl->curRL.type != change_cipher_spec &&
22710+
ssl->curRL.type != alert))) {
2268822711
if (!atomicUser
2268922712
#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY)
2269022713
&& !ssl->options.startedETMRead

tests/api/test_tls13.c

Lines changed: 220 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2339,7 +2339,6 @@ static int MERecv(WOLFSSL* ssl, char* buf, int sz, void* ctx)
23392339
int len = (int)msg->length;
23402340

23412341
(void)ssl;
2342-
(void)sz;
23432342

23442343
/* Pass back as much of message as will fit in buffer. */
23452344
if (len > sz)
@@ -2572,3 +2571,223 @@ int test_tls13_duplicate_extension(void)
25722571
}
25732572

25742573

2574+
2575+
#if defined(WOLFSSL_TLS13) && !defined(NO_RSA) && defined(HAVE_ECC) && \
2576+
defined(HAVE_AESGCM) && !defined(NO_WOLFSSL_SERVER)
2577+
/* Called when writing. */
2578+
static int Tls13PTASend(WOLFSSL* ssl, char* buf, int sz, void* ctx)
2579+
{
2580+
(void)ssl;
2581+
(void)buf;
2582+
(void)ctx;
2583+
2584+
return sz;
2585+
}
2586+
static int Tls13PTARecv(WOLFSSL* ssl, char* buf, int sz, void* ctx)
2587+
{
2588+
WOLFSSL_BUFFER_INFO* msg = (WOLFSSL_BUFFER_INFO*)ctx;
2589+
int len;
2590+
2591+
(void)ssl;
2592+
2593+
if (msg->length == 0) {
2594+
/* Only do as many alerts as required to get to max alert count. */
2595+
msg->buffer[0]--;
2596+
if (msg->buffer[0] > 0) {
2597+
msg->buffer -= 7;
2598+
msg->length += 7;
2599+
}
2600+
else {
2601+
return -1;
2602+
}
2603+
}
2604+
2605+
len = (int)msg->length;
2606+
/* Pass back as much of message as will fit in buffer. */
2607+
if (len > sz)
2608+
len = sz;
2609+
XMEMCPY(buf, msg->buffer, len);
2610+
/* Move over returned data. */
2611+
msg->buffer += len;
2612+
msg->length -= len;
2613+
2614+
/* Amount actually copied. */
2615+
return len;
2616+
}
2617+
#endif
2618+
2619+
int test_tls13_plaintext_alert(void)
2620+
{
2621+
EXPECT_DECLS;
2622+
2623+
#if defined(WOLFSSL_TLS13) && !defined(NO_RSA) && defined(HAVE_ECC) && \
2624+
defined(HAVE_AESGCM) && !defined(NO_WOLFSSL_SERVER)
2625+
byte clientMsgs[] = {
2626+
/* Client Hello */
2627+
0x16, 0x03, 0x03, 0x01, 0x9b, 0x01, 0x00, 0x01,
2628+
0x97, 0x03, 0x03, 0xf4, 0x65, 0xbd, 0x22, 0xfe,
2629+
0x6e, 0xab, 0x66, 0xdd, 0xcf, 0xe9, 0x65, 0x55,
2630+
0xe8, 0xdf, 0xc3, 0x8e, 0x4b, 0x00, 0xbc, 0xf8,
2631+
0x23, 0x57, 0x1b, 0xa0, 0xc8, 0xa9, 0xe2, 0x8c,
2632+
0x91, 0x6e, 0xf9, 0x20, 0xf7, 0x5c, 0xc5, 0x5b,
2633+
0x75, 0x8c, 0x47, 0x0a, 0x0e, 0xc4, 0x1a, 0xda,
2634+
0xef, 0x75, 0xe5, 0x21, 0x00, 0x00, 0x00, 0x00,
2635+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2636+
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x13, 0x01,
2637+
0x13, 0x02, 0x01, 0x00, 0x01, 0x4a, 0x00, 0x2d,
2638+
0x00, 0x03, 0x02, 0x00, 0x01, 0x00, 0x33, 0x00,
2639+
0x47, 0x00, 0x45, 0x00, 0x17, 0x00, 0x41, 0x04,
2640+
0x90, 0xfc, 0xe2, 0x97, 0x05, 0x7c, 0xb5, 0x23,
2641+
0x5d, 0x5f, 0x5b, 0xcd, 0x0c, 0x1e, 0xe0, 0xe9,
2642+
0xab, 0x38, 0x6b, 0x1e, 0x20, 0x5c, 0x1c, 0x90,
2643+
0x2a, 0x9e, 0x68, 0x8e, 0x70, 0x05, 0x10, 0xa8,
2644+
0x02, 0x1b, 0xf9, 0x5c, 0xef, 0xc9, 0xaf, 0xca,
2645+
0x1a, 0x3b, 0x16, 0x8b, 0xe4, 0x1b, 0x3c, 0x15,
2646+
0xb8, 0x0d, 0xbd, 0xaf, 0x62, 0x8d, 0xa7, 0x13,
2647+
0xa0, 0x7c, 0xe0, 0x59, 0x0c, 0x4f, 0x8a, 0x6d,
2648+
0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04, 0x00,
2649+
0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x03, 0x05,
2650+
0x03, 0x04, 0x03, 0x02, 0x03, 0x08, 0x06, 0x08,
2651+
0x0b, 0x08, 0x05, 0x08, 0x0a, 0x08, 0x04, 0x08,
2652+
0x09, 0x06, 0x01, 0x05, 0x01, 0x04, 0x01, 0x03,
2653+
0x01, 0x02, 0x01, 0x00, 0x0a, 0x00, 0x04, 0x00,
2654+
0x02, 0x00, 0x17, 0x00, 0x16, 0x00, 0x00, 0x00,
2655+
0x23, 0x00, 0x00, 0x00, 0x29, 0x00, 0xb9, 0x00,
2656+
0x94, 0x00, 0x8e, 0x0f, 0x12, 0xfa, 0x84, 0x1f,
2657+
0x76, 0x94, 0xd7, 0x09, 0x5e, 0xad, 0x08, 0x51,
2658+
0xb6, 0x80, 0x28, 0x31, 0x8b, 0xfd, 0xc6, 0xbd,
2659+
0x9e, 0xf5, 0x3b, 0x4d, 0x02, 0xbe, 0x1d, 0x73,
2660+
0xea, 0x13, 0x68, 0x00, 0x4c, 0xfd, 0x3d, 0x48,
2661+
0x51, 0xf9, 0x06, 0xbb, 0x92, 0xed, 0x42, 0x9f,
2662+
0x7f, 0x2c, 0x73, 0x9f, 0xd9, 0xb4, 0xef, 0x05,
2663+
0x26, 0x5b, 0x60, 0x5c, 0x0a, 0xfc, 0xa3, 0xbd,
2664+
0x2d, 0x2d, 0x8b, 0xf9, 0xaa, 0x5c, 0x96, 0x3a,
2665+
0xf2, 0xec, 0xfa, 0xe5, 0x57, 0x2e, 0x87, 0xbe,
2666+
0x27, 0xc5, 0x3d, 0x4f, 0x5d, 0xdd, 0xde, 0x1c,
2667+
0x1b, 0xb3, 0xcc, 0x27, 0x27, 0x57, 0x5a, 0xd9,
2668+
0xea, 0x99, 0x27, 0x23, 0xa6, 0x0e, 0xea, 0x9c,
2669+
0x0d, 0x85, 0xcb, 0x72, 0xeb, 0xd7, 0x93, 0xe3,
2670+
0xfe, 0xf7, 0x5c, 0xc5, 0x5b, 0x75, 0x8c, 0x47,
2671+
0x0a, 0x0e, 0xc4, 0x1a, 0xda, 0xef, 0x75, 0xe5,
2672+
0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2673+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2674+
0x00, 0xfb, 0x92, 0xce, 0xaa, 0x00, 0x21, 0x20,
2675+
0xcb, 0x73, 0x25, 0x80, 0x46, 0x78, 0x4f, 0xe5,
2676+
0x34, 0xf6, 0x91, 0x13, 0x7f, 0xc8, 0x8d, 0xdc,
2677+
0x81, 0x04, 0xb7, 0x0d, 0x49, 0x85, 0x2e, 0x12,
2678+
0x7a, 0x07, 0x23, 0xe9, 0x13, 0xa4, 0x6d, 0x8c,
2679+
0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x00, 0x00
2680+
};
2681+
2682+
WOLFSSL_CTX* ctx = NULL;
2683+
WOLFSSL* ssl = NULL;
2684+
WOLFSSL_BUFFER_INFO msg;
2685+
2686+
#ifdef WOLFSSL_TLS13_IGNORE_PT_ALERT_ON_ENC
2687+
/* We fail on WOLFSSL_ALERT_COUNT_MAX alerts. */
2688+
2689+
/* Set up wolfSSL context. */
2690+
ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
2691+
ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile,
2692+
CERT_FILETYPE));
2693+
ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile,
2694+
CERT_FILETYPE));
2695+
if (EXPECT_SUCCESS()) {
2696+
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_NONE, NULL);
2697+
}
2698+
/* Read from 'msg'. */
2699+
wolfSSL_SetIORecv(ctx, Tls13PTARecv);
2700+
/* No where to send to - dummy sender. */
2701+
wolfSSL_SetIOSend(ctx, Tls13PTASend);
2702+
2703+
ExpectNotNull(ssl = wolfSSL_new(ctx));
2704+
msg.buffer = clientMsgs;
2705+
msg.length = (unsigned int)sizeof(clientMsgs) - 1;
2706+
clientMsgs[sizeof(clientMsgs) - 1] = WOLFSSL_ALERT_COUNT_MAX;
2707+
if (EXPECT_SUCCESS()) {
2708+
wolfSSL_SetIOReadCtx(ssl, &msg);
2709+
}
2710+
/* Alert will be ignored until too many. */
2711+
/* Read all message include CertificateVerify with invalid signature
2712+
* algorithm. */
2713+
ExpectIntEQ(wolfSSL_accept(ssl), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR));
2714+
/* Expect an invalid parameter error. */
2715+
ExpectIntEQ(wolfSSL_get_error(ssl, WOLFSSL_FATAL_ERROR),
2716+
WC_NO_ERR_TRACE(ALERT_COUNT_E));
2717+
2718+
wolfSSL_free(ssl);
2719+
ssl = NULL;
2720+
wolfSSL_CTX_free(ctx);
2721+
ctx = NULL;
2722+
2723+
/* Set up wolfSSL context. */
2724+
ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
2725+
ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile,
2726+
CERT_FILETYPE));
2727+
ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile,
2728+
CERT_FILETYPE));
2729+
if (EXPECT_SUCCESS()) {
2730+
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_NONE, NULL);
2731+
}
2732+
/* Read from 'msg'. */
2733+
wolfSSL_SetIORecv(ctx, Tls13PTARecv);
2734+
/* No where to send to - dummy sender. */
2735+
wolfSSL_SetIOSend(ctx, Tls13PTASend);
2736+
2737+
ExpectNotNull(ssl = wolfSSL_new(ctx));
2738+
msg.buffer = clientMsgs;
2739+
msg.length = (unsigned int)sizeof(clientMsgs) - 1;
2740+
clientMsgs[sizeof(clientMsgs) - 1] = WOLFSSL_ALERT_COUNT_MAX - 1;
2741+
if (EXPECT_SUCCESS()) {
2742+
wolfSSL_SetIOReadCtx(ssl, &msg);
2743+
}
2744+
/* Alert will be ignored until too many. */
2745+
/* Read all message include CertificateVerify with invalid signature
2746+
* algorithm. */
2747+
ExpectIntEQ(wolfSSL_accept(ssl), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR));
2748+
/* Expect an invalid parameter error. */
2749+
ExpectIntEQ(wolfSSL_get_error(ssl, WOLFSSL_FATAL_ERROR),
2750+
WC_NO_ERR_TRACE(SOCKET_ERROR_E));
2751+
2752+
wolfSSL_free(ssl);
2753+
wolfSSL_CTX_free(ctx);
2754+
#else
2755+
/* Fail on plaintext alert when encryption keys on. */
2756+
2757+
/* Set up wolfSSL context. */
2758+
ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
2759+
ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile,
2760+
CERT_FILETYPE));
2761+
ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile,
2762+
CERT_FILETYPE));
2763+
if (EXPECT_SUCCESS()) {
2764+
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_NONE, NULL);
2765+
}
2766+
/* Read from 'msg'. */
2767+
wolfSSL_SetIORecv(ctx, Tls13PTARecv);
2768+
/* No where to send to - dummy sender. */
2769+
wolfSSL_SetIOSend(ctx, Tls13PTASend);
2770+
2771+
ExpectNotNull(ssl = wolfSSL_new(ctx));
2772+
msg.buffer = clientMsgs;
2773+
msg.length = (unsigned int)sizeof(clientMsgs) - 1;
2774+
clientMsgs[sizeof(clientMsgs) - 1] = 1;
2775+
if (EXPECT_SUCCESS()) {
2776+
wolfSSL_SetIOReadCtx(ssl, &msg);
2777+
}
2778+
/* Alert will be ignored until too many. */
2779+
/* Read all message include CertificateVerify with invalid signature
2780+
* algorithm. */
2781+
ExpectIntEQ(wolfSSL_accept(ssl), WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR));
2782+
/* Expect an invalid parameter error. */
2783+
ExpectIntEQ(wolfSSL_get_error(ssl, WOLFSSL_FATAL_ERROR),
2784+
WC_NO_ERR_TRACE(PARSE_ERROR));
2785+
2786+
wolfSSL_free(ssl);
2787+
wolfSSL_CTX_free(ctx);
2788+
#endif
2789+
#endif
2790+
2791+
return EXPECT_RESULT();
2792+
}
2793+

0 commit comments

Comments
 (0)