// SPDX-FileCopyrightText: © 2022—2026 Vladimir Zorin // SPDX-License-Identifier: LicenseRef-OWL-1.0-or-later // Licensed under OWL v1.0+. See LICENSE. /* * LITLS -- TLS 1.3 Client State Machine * * Drives the TLS 1.3 handshake: sends ClientHello, processes server messages, * derives keys, validates certificates, and completes the handshake. */ #include "tls13.h" #include #include #include /* ── Helpers ──────────────────────────────────────────────────────── */ /* ── Initialization ───────────────────────────────────────────────── */ int litls_tls13_client_init(litls_tls13_ctx *ctx, int fd, int verify, const char *sni, const litls_x509_trust_anchor *anchors, int anchor_count, litls_server_identity *client_identity) { memset(ctx, 0, sizeof(*ctx)); ctx->fd = fd; ctx->verify = verify; ctx->anchors = anchors; ctx->anchor_count = anchor_count; ctx->client_identity = client_identity; ctx->hs_state = TLS13_HS_SEND_CLIENT_HELLO; if (sni && sni[0]) { size_t sni_len = strlen(sni); if (sni_len >= TLS13_MAX_SNI_LEN) { snprintf(ctx->error, sizeof(ctx->error), "SNI too long"); return -1; } memcpy(ctx->sni, sni, sni_len + 1); } /* Generate ephemeral X25519 keypair */ if (litls_random_bytes(ctx->x25519_priv, 32) != 0) { snprintf(ctx->error, sizeof(ctx->error), "RNG failed"); return -1; } litls_x25519_base(ctx->x25519_pub, ctx->x25519_priv); litls_transcript_init(&ctx->transcript); return 0; } /* ── CertificateVerify validation ─────────────────────────────────── */ /* Build the content that was signed for CertificateVerify: * 64 spaces || "TLS 1.3, server CertificateVerify" || 0x00 || Hash(Transcript) */ static int build_cv_content(litls_tls13_ctx *ctx, uint8_t *out, size_t *out_len) { uint8_t transcript_hash[48]; litls_transcript_hash(&ctx->transcript, transcript_hash); int hash_len = ctx->suite.hash_len; size_t pos = 0; memset(out, 0x20, 64); pos += 64; static const char label[] = "TLS 1.3, server CertificateVerify"; memcpy(out + pos, label, sizeof(label) - 1); pos += sizeof(label) - 1; out[pos++] = 0x00; memcpy(out + pos, transcript_hash, (size_t)hash_len); pos += (size_t)hash_len; litls_secure_zero(transcript_hash, sizeof(transcript_hash)); *out_len = pos; return 0; } static int verify_certificate_verify(litls_tls13_ctx *ctx, uint16_t sig_algo, const uint8_t *sig, size_t sig_len, const litls_x509_cert_info *leaf) { uint8_t content[200]; size_t content_len; build_cv_content(ctx, content, &content_len); int ret = -1; switch (sig_algo) { case TLS13_SIG_ECDSA_SECP256R1_SHA256: { if (leaf->key_type != LITLS_KEY_ECDSA_P256) { snprintf(ctx->error, sizeof(ctx->error), "CertificateVerify: key type mismatch for ECDSA"); goto out; } uint8_t hash[32]; litls_sha256(content, content_len, hash); uint8_t raw_sig[64]; if (litls_ecdsa_sig_der_to_raw(sig, sig_len, raw_sig, 32) != 0) { snprintf(ctx->error, sizeof(ctx->error), "CertificateVerify: invalid ECDSA DER signature"); goto out; } if (litls_p256_ecdsa_verify(hash, 32, leaf->pubkey, raw_sig, 64) != 0) { snprintf(ctx->error, sizeof(ctx->error), "CertificateVerify: ECDSA verify failed"); goto out; } ret = 0; goto out; } case TLS13_SIG_RSA_PSS_RSAE_SHA256: { if (leaf->key_type != LITLS_KEY_RSA) { snprintf(ctx->error, sizeof(ctx->error), "CertificateVerify: key type mismatch for RSA-PSS"); goto out; } uint8_t hash[32]; litls_sha256(content, content_len, hash); if (litls_rsa_pss_sha256_verify(hash, sig, sig_len, leaf->pubkey, leaf->pubkey_len, leaf->rsa_e, leaf->rsa_e_len) != 0) { snprintf(ctx->error, sizeof(ctx->error), "CertificateVerify: RSA-PSS verify failed"); goto out; } ret = 0; goto out; } case TLS13_SIG_ED25519: { if (sig_len != 64) { snprintf(ctx->error, sizeof(ctx->error), "CertificateVerify: invalid Ed25519 signature length"); goto out; } if (litls_ed25519_verify(sig, content, content_len, leaf->pubkey) != 0) { snprintf(ctx->error, sizeof(ctx->error), "CertificateVerify: Ed25519 verify failed"); goto out; } ret = 0; goto out; } default: /* RFC 8446 §4.2.3 explicitly forbids rsa_pkcs1_* in TLS 1.3 * CertificateVerify; RSA peers must use RSA-PSS. Anything not * matched above (including 0x0401) is rejected here. */ snprintf(ctx->error, sizeof(ctx->error), "CertificateVerify: unsupported sig_algo 0x%04X", sig_algo); goto out; } out: litls_secure_zero(content, sizeof(content)); return ret; } /* ── Finished verify ──────────────────────────────────────────────── */ static int verify_server_finished(litls_tls13_ctx *ctx, const uint8_t *verify_data, size_t verify_len) { int hash_len = ctx->suite.hash_len; uint8_t transcript_hash[48]; litls_transcript_hash(&ctx->transcript, transcript_hash); uint8_t finished_key[48]; uint8_t expected[48]; int ret = -1; if (hash_len == 32) { litls_tls13_expand_label_256(ctx->server_hs_secret, "finished", NULL, 0, finished_key, 32); litls_hmac_sha256(finished_key, 32, transcript_hash, 32, expected); if (litls_secure_memcmp(verify_data, expected, 32) != 0) { snprintf(ctx->error, sizeof(ctx->error), "server Finished verify failed"); } else { ret = 0; } } else { litls_tls13_expand_label_384(ctx->server_hs_secret, "finished", NULL, 0, finished_key, 48); litls_hmac_sha384(finished_key, 48, transcript_hash, 48, expected); if (litls_secure_memcmp(verify_data, expected, 48) != 0) { snprintf(ctx->error, sizeof(ctx->error), "server Finished verify failed"); } else { ret = 0; } } litls_secure_zero(finished_key, sizeof(finished_key)); litls_secure_zero(expected, sizeof(expected)); litls_secure_zero(transcript_hash, sizeof(transcript_hash)); return ret; } /* build_client_finished uses the shared litls_build_finished helper */ /* ── HelloRetryRequest handling ───────────────────────────────────── */ static int handle_hrr(litls_tls13_ctx *ctx) { /* Replace transcript with synthetic hash per RFC 8446 Section 4.4.1: * Hash(message_hash || 00 00 Hash.length || Hash(CH1)) * i.e. replace the running transcript with a special construct: * message_hash(254) + 00 00 + */ int hash_len = ctx->suite.hash_len; uint8_t old_hash[48]; litls_transcript_hash(&ctx->transcript, old_hash); /* Re-init transcript and feed the synthetic message */ litls_transcript_init(&ctx->transcript); /* Lock to the selected hash */ ctx->transcript.use_384 = (hash_len == 48) ? 1 : 0; uint8_t synthetic[4 + 48]; /* message_hash header + hash */ synthetic[0] = 254; /* message_hash type */ synthetic[1] = 0; synthetic[2] = 0; synthetic[3] = (uint8_t)hash_len; memcpy(synthetic + 4, old_hash, (size_t)hash_len); litls_transcript_update(&ctx->transcript, synthetic, 4 + (size_t)hash_len); /* Regenerate X25519 keypair */ if (litls_random_bytes(ctx->x25519_priv, 32) != 0) { snprintf(ctx->error, sizeof(ctx->error), "RNG failed for HRR"); return -1; } litls_x25519_base(ctx->x25519_pub, ctx->x25519_priv); return 0; } /* ── State machine ────────────────────────────────────────────────── */ litls_io_want litls_tls13_handshake_step(litls_tls13_ctx *ctx) { switch (ctx->hs_state) { case TLS13_HS_SEND_CLIENT_HELLO: { uint8_t ch_buf[1024]; size_t ch_len; if (litls_build_client_hello(ctx, ch_buf, sizeof(ch_buf), &ch_len) != 0) return LITLS_ERROR; /* Update transcript with ClientHello */ litls_transcript_update(&ctx->transcript, ch_buf, ch_len); /* Write as a handshake record */ if (litls_record_write(ctx, TLS13_CT_HANDSHAKE, ch_buf, ch_len) != 0) return LITLS_ERROR; ctx->hs_state = TLS13_HS_RECV_SERVER_HELLO; return LITLS_WANT_WRITE; } case TLS13_HS_RECV_SERVER_HELLO: { uint8_t ct; uint8_t *data; size_t data_len; int rc = litls_record_read(ctx, &ct, &data, &data_len); if (rc == 1) return LITLS_WANT_READ; /* incomplete */ if (rc < 0) { if (rc == -2) return LITLS_CLOSED; return LITLS_ERROR; } if (ct != TLS13_CT_HANDSHAKE || data_len < 4) { snprintf(ctx->error, sizeof(ctx->error), "expected handshake, got type %d", ct); return LITLS_ERROR; } if (data[0] != TLS13_HT_SERVER_HELLO) { snprintf(ctx->error, sizeof(ctx->error), "expected ServerHello, got type %d", data[0]); return LITLS_ERROR; } /* Parse ServerHello body (skip 4-byte header) first -- this sets * ctx->hrr_seen if this is actually a HelloRetryRequest. The * transcript update is done AFTER parsing because HRR requires * a different transcript order per RFC 8446 §4.4.1: the synthetic * message_hash must hash only ClientHello1, then HRR is appended. */ uint32_t msg_len = ((uint32_t)data[1] << 16) | ((uint32_t)data[2] << 8) | data[3]; if (4 + msg_len > data_len) { snprintf(ctx->error, sizeof(ctx->error), "ServerHello length mismatch"); return LITLS_ERROR; } if (litls_parse_server_hello(ctx, data + 4, msg_len) != 0) return LITLS_ERROR; if (ctx->hrr_seen) { /* Transcript at this point still contains only ClientHello1. * handle_hrr() replaces it with the synthetic message_hash of CH1. */ if (handle_hrr(ctx) != 0) return LITLS_ERROR; /* Then append the HRR (ServerHello) once into the reset transcript. */ litls_transcript_update(&ctx->transcript, data, data_len); ctx->hrr_seen = 0; ctx->hrr_done = 1; ctx->hs_state = TLS13_HS_SEND_CLIENT_HELLO; return LITLS_WANT_WRITE; } /* Non-HRR ServerHello: update transcript as usual. */ litls_transcript_update(&ctx->transcript, data, data_len); /* Derive handshake keys */ if (litls_tls13_derive_handshake_keys(ctx) != 0) { snprintf(ctx->error, sizeof(ctx->error), "handshake key derivation failed"); return LITLS_ERROR; } /* Install handshake read/write keys */ ctx->decrypting = 1; ctx->encrypting = 1; ctx->hs_state = TLS13_HS_RECV_ENCRYPTED_EXTENSIONS; ctx->hs_buf_len = 0; return LITLS_WANT_READ; } case TLS13_HS_RECV_ENCRYPTED_EXTENSIONS: { uint8_t msg_type; const uint8_t *msg; size_t msg_len; int rc = litls_get_hs_message(ctx, &msg_type, &msg, &msg_len); if (rc == 0) return LITLS_WANT_READ; if (rc < 0) return (rc == -2) ? LITLS_CLOSED : LITLS_ERROR; litls_transcript_update(&ctx->transcript, ctx->hs_buf, 4 + msg_len); if (msg_type != TLS13_HT_ENCRYPTED_EXTENSIONS) { snprintf(ctx->error, sizeof(ctx->error), "expected EncryptedExtensions, got type %d", msg_type); return LITLS_ERROR; } if (litls_parse_encrypted_extensions(ctx, msg, msg_len) != 0) return LITLS_ERROR; litls_consume_hs_message(ctx, msg_len); ctx->hs_state = TLS13_HS_RECV_CERTIFICATE; return LITLS_WANT_READ; } case TLS13_HS_RECV_CERTIFICATE: { uint8_t msg_type; const uint8_t *msg; size_t msg_len; int rc = litls_get_hs_message(ctx, &msg_type, &msg, &msg_len); if (rc == 0) return LITLS_WANT_READ; if (rc < 0) return (rc == -2) ? LITLS_CLOSED : LITLS_ERROR; /* Handle optional CertificateRequest (mTLS) before server Certificate */ if (msg_type == TLS13_HT_CERTIFICATE_REQUEST) { litls_transcript_update(&ctx->transcript, ctx->hs_buf, 4 + msg_len); if (litls_parse_certificate_request(ctx, msg, msg_len) != 0) return LITLS_ERROR; litls_consume_hs_message(ctx, msg_len); return LITLS_WANT_READ; /* stay in this state for the actual Certificate */ } litls_transcript_update(&ctx->transcript, ctx->hs_buf, 4 + msg_len); if (msg_type != TLS13_HT_CERTIFICATE) { snprintf(ctx->error, sizeof(ctx->error), "expected Certificate, got type %d", msg_type); return LITLS_ERROR; } /* Parse certificate chain */ const uint8_t *chain_ders[LITLS_X509_MAX_CHAIN]; size_t chain_lens[LITLS_X509_MAX_CHAIN]; int chain_count = 0; if (litls_parse_certificate(ctx, msg, msg_len, chain_ders, chain_lens, &chain_count, LITLS_X509_MAX_CHAIN) != 0) return LITLS_ERROR; /* Parse leaf certificate for CertificateVerify */ if (litls_x509_parse(chain_ders[0], chain_lens[0], &ctx->leaf_cert) != 0) { snprintf(ctx->error, sizeof(ctx->error), "failed to parse leaf certificate"); return LITLS_ERROR; } /* Validate certificate chain if verify is enabled */ if (ctx->verify) { if (!ctx->anchors || ctx->anchor_count <= 0) { snprintf(ctx->error, sizeof(ctx->error), "no trust anchors for verification"); return LITLS_ERROR; } int64_t now = (int64_t)time(NULL); int vrc = litls_x509_verify_chain(chain_ders, chain_lens, chain_count, ctx->anchors, ctx->anchor_count, now); if (vrc != LITLS_X509_OK) { switch (vrc) { case LITLS_X509_ERR_PARSE: snprintf(ctx->error, sizeof(ctx->error), "certificate parse error"); break; case LITLS_X509_ERR_SIGNATURE: snprintf(ctx->error, sizeof(ctx->error), "certificate signature invalid"); break; case LITLS_X509_ERR_EXPIRED: snprintf(ctx->error, sizeof(ctx->error), "certificate expired"); break; case LITLS_X509_ERR_NO_ANCHOR: snprintf(ctx->error, sizeof(ctx->error), "no matching trust anchor"); break; case LITLS_X509_ERR_CONSTRAINT: snprintf(ctx->error, sizeof(ctx->error), "certificate constraint violation"); break; default: snprintf(ctx->error, sizeof(ctx->error), "certificate chain invalid"); break; } return LITLS_ERROR; } /* RFC 6125: verify the hostname (SNI) matches the leaf's SAN or CN. * Skipping this step would let any cert chaining to a trusted CA * authenticate any hostname. */ if (ctx->sni[0] == '\0') { snprintf(ctx->error, sizeof(ctx->error), "hostname verification requires SNI"); return LITLS_ERROR; } if (litls_x509_verify_hostname(&ctx->leaf_cert, ctx->sni) != LITLS_X509_OK) { snprintf(ctx->error, sizeof(ctx->error), "certificate hostname mismatch: %s", ctx->sni); return LITLS_ERROR; } } litls_consume_hs_message(ctx, msg_len); ctx->hs_state = TLS13_HS_RECV_CERTIFICATE_VERIFY; return LITLS_WANT_READ; } case TLS13_HS_RECV_CERTIFICATE_VERIFY: { uint8_t msg_type; const uint8_t *msg; size_t msg_len; int rc = litls_get_hs_message(ctx, &msg_type, &msg, &msg_len); if (rc == 0) return LITLS_WANT_READ; if (rc < 0) return (rc == -2) ? LITLS_CLOSED : LITLS_ERROR; if (msg_type != TLS13_HT_CERTIFICATE_VERIFY) { snprintf(ctx->error, sizeof(ctx->error), "expected CertificateVerify, got type %d", msg_type); return LITLS_ERROR; } /* Parse signature */ uint16_t sig_algo; const uint8_t *sig; size_t sig_len; if (litls_parse_certificate_verify(ctx, msg, msg_len, &sig_algo, &sig, &sig_len) != 0) return LITLS_ERROR; /* Verify the signature (uses transcript hash BEFORE this message). * `ctx->verify` toggles trust-anchor and hostname validation only; * the CertificateVerify signature is a structural protocol check * (does the peer own the key in the cert it just sent?) and runs * unconditionally. */ if (verify_certificate_verify(ctx, sig_algo, sig, sig_len, &ctx->leaf_cert) != 0) return LITLS_ERROR; /* NOW update transcript with CertificateVerify */ litls_transcript_update(&ctx->transcript, ctx->hs_buf, 4 + msg_len); litls_consume_hs_message(ctx, msg_len); ctx->hs_state = TLS13_HS_RECV_FINISHED; return LITLS_WANT_READ; } case TLS13_HS_RECV_FINISHED: { uint8_t msg_type; const uint8_t *msg; size_t msg_len; int rc = litls_get_hs_message(ctx, &msg_type, &msg, &msg_len); if (rc == 0) return LITLS_WANT_READ; if (rc < 0) return (rc == -2) ? LITLS_CLOSED : LITLS_ERROR; if (msg_type != TLS13_HT_FINISHED) { snprintf(ctx->error, sizeof(ctx->error), "expected Finished, got type %d", msg_type); return LITLS_ERROR; } const uint8_t *verify_data; size_t verify_len; if (litls_parse_finished(ctx, msg, msg_len, &verify_data, &verify_len) != 0) return LITLS_ERROR; /* Verify server Finished (uses transcript hash BEFORE Finished message) */ if (verify_server_finished(ctx, verify_data, verify_len) != 0) return LITLS_ERROR; /* Update transcript with server Finished */ litls_transcript_update(&ctx->transcript, ctx->hs_buf, 4 + msg_len); /* Derive application keys (uses transcript through server Finished) */ if (litls_tls13_derive_app_keys(ctx) != 0) { snprintf(ctx->error, sizeof(ctx->error), "app key derivation failed"); return LITLS_ERROR; } litls_consume_hs_message(ctx, msg_len); ctx->hs_state = ctx->cert_requested ? TLS13_HS_SEND_CLIENT_CERTIFICATE : TLS13_HS_SEND_FINISHED; return LITLS_WANT_WRITE; } case TLS13_HS_SEND_CLIENT_CERTIFICATE: { uint8_t cert_buf[LITLS_MAX_CERT_CHAIN_DER + 256]; size_t cert_len; if (litls_build_certificate(ctx->client_identity, ctx->cert_request_ctx, ctx->cert_request_ctx_len, cert_buf, sizeof(cert_buf), &cert_len) != 0) { snprintf(ctx->error, sizeof(ctx->error), "failed to build client Certificate"); return LITLS_ERROR; } litls_transcript_update(&ctx->transcript, cert_buf, cert_len); if (litls_record_write(ctx, TLS13_CT_HANDSHAKE, cert_buf, cert_len) != 0) return LITLS_ERROR; if (ctx->client_identity && ctx->client_identity->cert_count > 0) ctx->hs_state = TLS13_HS_SEND_CLIENT_CERTIFICATE_VERIFY; else ctx->hs_state = TLS13_HS_SEND_FINISHED; return LITLS_WANT_WRITE; } case TLS13_HS_SEND_CLIENT_CERTIFICATE_VERIFY: { uint8_t cv_buf[256]; size_t cv_len; if (litls_build_certificate_verify(ctx, ctx->client_identity, 0, cv_buf, sizeof(cv_buf), &cv_len) != 0) { snprintf(ctx->error, sizeof(ctx->error), "failed to build client CertificateVerify"); return LITLS_ERROR; } if (litls_record_write(ctx, TLS13_CT_HANDSHAKE, cv_buf, cv_len) != 0) return LITLS_ERROR; /* Update transcript AFTER building signature (sig covers hash before CV) */ litls_transcript_update(&ctx->transcript, cv_buf, cv_len); ctx->hs_state = TLS13_HS_SEND_FINISHED; return LITLS_WANT_WRITE; } case TLS13_HS_SEND_FINISHED: { uint8_t fin_buf[56]; /* 4 header + up to 48 verify_data */ size_t fin_len; if (litls_build_finished(ctx, ctx->client_hs_secret, fin_buf, &fin_len) != 0) { snprintf(ctx->error, sizeof(ctx->error), "failed to build client Finished"); return LITLS_ERROR; } /* Update transcript with client Finished */ litls_transcript_update(&ctx->transcript, fin_buf, fin_len); /* Write as encrypted handshake record */ if (litls_record_write(ctx, TLS13_CT_HANDSHAKE, fin_buf, fin_len) != 0) return LITLS_ERROR; /* Handshake complete - switch to application keys */ ctx->hs_state = TLS13_HS_DONE; return LITLS_WANT_WRITE; } case TLS13_HS_DONE: return LITLS_DONE; default: break; } snprintf(ctx->error, sizeof(ctx->error), "invalid handshake state"); return LITLS_ERROR; } /* ── Application data ─────────────────────────────────────────────── */ int litls_tls13_write(litls_tls13_ctx *ctx, const uint8_t *data, size_t len) { if (ctx->hs_state != TLS13_HS_DONE) { snprintf(ctx->error, sizeof(ctx->error), "not connected"); return -1; } /* Fragment into max record-sized chunks, flushing between records */ size_t offset = 0; while (offset < len) { /* Flush pending data before writing a new record */ if (ctx->wbuf.len > 0) { litls_io_want w = litls_record_flush(ctx); if (w == LITLS_WANT_WRITE) { if (offset > 0) return (int)offset; snprintf(ctx->error, sizeof(ctx->error), "want_write"); return -1; } if (w == LITLS_ERROR) return (offset > 0) ? (int)offset : -1; } size_t chunk = len - offset; if (chunk > TLS13_MAX_RECORD) chunk = TLS13_MAX_RECORD; if (litls_record_write(ctx, TLS13_CT_APPLICATION_DATA, data + offset, chunk) != 0) return (offset > 0) ? (int)offset : -1; offset += chunk; } return (int)len; } int litls_tls13_read(litls_tls13_ctx *ctx, uint8_t *buf, size_t cap) { /* Return buffered application data first */ if (ctx->app_buf_len > ctx->app_buf_pos) { size_t avail = ctx->app_buf_len - ctx->app_buf_pos; size_t n = (avail < cap) ? avail : cap; memcpy(buf, ctx->app_buf + ctx->app_buf_pos, n); ctx->app_buf_pos += n; if (ctx->app_buf_pos == ctx->app_buf_len) { ctx->app_buf_pos = 0; ctx->app_buf_len = 0; } return (int)n; } if (ctx->peer_closed) return -2; /* closed */ /* Try to read a record */ uint8_t ct; uint8_t *data; size_t data_len; int rc = litls_record_read(ctx, &ct, &data, &data_len); if (rc == 1) return 0; /* need more data (WANT_READ) */ if (rc < 0) return rc; /* error or closed */ if (ct == TLS13_CT_APPLICATION_DATA) { size_t n = (data_len < cap) ? data_len : cap; memcpy(buf, data, n); /* Buffer remainder */ if (data_len > n) { size_t rem = data_len - n; if (rem > TLS13_APP_BUF_SIZE) { snprintf(ctx->error, sizeof(ctx->error), "app data overflow"); return -1; } memcpy(ctx->app_buf, data + n, rem); ctx->app_buf_len = rem; ctx->app_buf_pos = 0; } return (int)n; } if (ct == TLS13_CT_HANDSHAKE) { /* Post-handshake messages */ if (data_len >= 4 && data[0] == TLS13_HT_NEW_SESSION_TICKET) { /* Silently discard NewSessionTicket */ return 0; /* signal WANT_READ to caller */ } if (data_len >= 4 && data[0] == TLS13_HT_KEY_UPDATE) { snprintf(ctx->error, sizeof(ctx->error), "KeyUpdate not supported"); return -1; } } snprintf(ctx->error, sizeof(ctx->error), "unexpected content type %d in app data", ct); return -1; } /* ── Shutdown ─────────────────────────────────────────────────────── */ int litls_tls13_shutdown(litls_tls13_ctx *ctx) { if (ctx->hs_state == TLS13_HS_DONE) { /* Send close_notify alert */ uint8_t alert[2] = {1, TLS13_ALERT_CLOSE_NOTIFY}; /* warning level, close_notify */ litls_record_write(ctx, TLS13_CT_ALERT, alert, 2); /* Best-effort flush */ litls_record_flush(ctx); } /* Zero all key material regardless of state */ litls_secure_zero(ctx->x25519_priv, sizeof(ctx->x25519_priv)); litls_secure_zero(ctx->shared_secret, sizeof(ctx->shared_secret)); litls_secure_zero(ctx->server_hs_secret, sizeof(ctx->server_hs_secret)); litls_secure_zero(ctx->client_hs_secret, sizeof(ctx->client_hs_secret)); litls_secure_zero(ctx->master_secret, sizeof(ctx->master_secret)); litls_secure_zero(&ctx->hs_read, sizeof(ctx->hs_read)); litls_secure_zero(&ctx->hs_write, sizeof(ctx->hs_write)); litls_secure_zero(&ctx->app_read, sizeof(ctx->app_read)); litls_secure_zero(&ctx->app_write, sizeof(ctx->app_write)); return 0; }