| /* |
| * Copyright 2018-2026 The OpenSSL Project Authors. All Rights Reserved. |
| * |
| * Licensed under the Apache License 2.0 (the "License"). You may not use |
| * this file except in compliance with the License. You can obtain a copy |
| * in the file LICENSE in the source distribution or at |
| * https://www.openssl.org/source/license.html |
| */ |
| |
| #include <openssl/evp.h> |
| #include <openssl/core_names.h> |
| #include <openssl/rand.h> |
| #include "../../ssl_local.h" |
| #include "../record_local.h" |
| #include "recmethod_local.h" |
| #include "internal/ktls.h" |
| |
| static struct record_functions_st ossl_ktls_funcs; |
| |
| #if defined(__FreeBSD__) |
| #include "crypto/cryptodev.h" |
| |
| /*- |
| * Check if a given cipher is supported by the KTLS interface. |
| * The kernel might still fail the setsockopt() if no suitable |
| * provider is found, but this checks if the socket option |
| * supports the cipher suite used at all. |
| */ |
| static int ktls_int_check_supported_cipher(OSSL_RECORD_LAYER *rl, |
| const EVP_CIPHER *c, |
| const EVP_MD *md, |
| size_t taglen) |
| { |
| switch (rl->version) { |
| case TLS1_VERSION: |
| case TLS1_1_VERSION: |
| case TLS1_2_VERSION: |
| #ifdef OPENSSL_KTLS_TLS13 |
| case TLS1_3_VERSION: |
| #endif |
| break; |
| default: |
| return 0; |
| } |
| |
| if (EVP_CIPHER_is_a(c, "AES-128-GCM") |
| || EVP_CIPHER_is_a(c, "AES-256-GCM") |
| #ifdef OPENSSL_KTLS_CHACHA20_POLY1305 |
| || EVP_CIPHER_is_a(c, "CHACHA20-POLY1305") |
| #endif |
| ) |
| return 1; |
| |
| if (!EVP_CIPHER_is_a(c, "AES-128-CBC") |
| && !EVP_CIPHER_is_a(c, "AES-256-CBC")) |
| return 0; |
| |
| if (rl->use_etm) |
| return 0; |
| |
| if (md == NULL) |
| return 0; |
| |
| if (EVP_MD_is_a(md, "SHA1") |
| || EVP_MD_is_a(md, "SHA2-256") |
| || EVP_MD_is_a(md, "SHA2-384")) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Function to configure kernel TLS structure */ |
| static int ktls_configure_crypto(OSSL_LIB_CTX *libctx, int version, const EVP_CIPHER *c, |
| EVP_MD *md, void *rl_sequence, |
| ktls_crypto_info_t *crypto_info, int is_tx, |
| unsigned char *iv, size_t ivlen, |
| unsigned char *key, size_t keylen, |
| unsigned char *mac_key, size_t mac_secret_size) |
| { |
| memset(crypto_info, 0, sizeof(*crypto_info)); |
| if (EVP_CIPHER_is_a(c, "AES-128-GCM") |
| || EVP_CIPHER_is_a(c, "AES-256-GCM")) { |
| crypto_info->cipher_algorithm = CRYPTO_AES_NIST_GCM_16; |
| crypto_info->iv_len = ivlen; |
| } else |
| #ifdef OPENSSL_KTLS_CHACHA20_POLY1305 |
| if (EVP_CIPHER_is_a(c, "CHACHA20-POLY1305")) { |
| crypto_info->cipher_algorithm = CRYPTO_CHACHA20_POLY1305; |
| crypto_info->iv_len = ivlen; |
| } else |
| #endif |
| if (EVP_CIPHER_is_a(c, "AES-128-CBC") || EVP_CIPHER_is_a(c, "AES-256-CBC")) { |
| if (md == NULL) |
| return 0; |
| if (EVP_MD_is_a(md, "SHA1")) |
| crypto_info->auth_algorithm = CRYPTO_SHA1_HMAC; |
| else if (EVP_MD_is_a(md, "SHA2-256")) |
| crypto_info->auth_algorithm = CRYPTO_SHA2_256_HMAC; |
| else if (EVP_MD_is_a(md, "SHA2-384")) |
| crypto_info->auth_algorithm = CRYPTO_SHA2_384_HMAC; |
| else |
| return 0; |
| crypto_info->cipher_algorithm = CRYPTO_AES_CBC; |
| crypto_info->iv_len = ivlen; |
| crypto_info->auth_key = mac_key; |
| crypto_info->auth_key_len = mac_secret_size; |
| } else { |
| return 0; |
| } |
| crypto_info->cipher_key = key; |
| crypto_info->cipher_key_len = keylen; |
| crypto_info->iv = iv; |
| crypto_info->tls_vmajor = (version >> 8) & 0x000000ff; |
| crypto_info->tls_vminor = (version & 0x000000ff); |
| #ifdef TCP_RXTLS_ENABLE |
| memcpy(crypto_info->rec_seq, rl_sequence, sizeof(crypto_info->rec_seq)); |
| #else |
| if (!is_tx) |
| return 0; |
| #endif |
| return 1; |
| }; |
| |
| #endif /* __FreeBSD__ */ |
| |
| #if defined(OPENSSL_SYS_LINUX) |
| /* Function to check supported ciphers in Linux */ |
| static int ktls_int_check_supported_cipher(OSSL_RECORD_LAYER *rl, |
| const EVP_CIPHER *c, |
| const EVP_MD *md, |
| size_t taglen) |
| { |
| switch (rl->version) { |
| case TLS1_2_VERSION: |
| #ifdef OPENSSL_KTLS_TLS13 |
| case TLS1_3_VERSION: |
| #endif |
| break; |
| default: |
| return 0; |
| } |
| |
| /* |
| * Check that cipher is AES_GCM_128, AES_GCM_256, AES_CCM_128 |
| * or Chacha20-Poly1305 |
| */ |
| #ifdef OPENSSL_KTLS_AES_CCM_128 |
| if (EVP_CIPHER_is_a(c, "AES-128-CCM")) { |
| if (taglen != EVP_CCM_TLS_TAG_LEN) |
| return 0; |
| return 1; |
| } else |
| #endif |
| if (0 |
| #ifdef OPENSSL_KTLS_AES_GCM_128 |
| || EVP_CIPHER_is_a(c, "AES-128-GCM") |
| #endif |
| #ifdef OPENSSL_KTLS_AES_GCM_256 |
| || EVP_CIPHER_is_a(c, "AES-256-GCM") |
| #endif |
| #ifdef OPENSSL_KTLS_CHACHA20_POLY1305 |
| || EVP_CIPHER_is_a(c, "ChaCha20-Poly1305") |
| #endif |
| ) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Function to configure kernel TLS structure */ |
| static int ktls_configure_crypto(OSSL_LIB_CTX *libctx, int version, const EVP_CIPHER *c, |
| const EVP_MD *md, void *rl_sequence, |
| ktls_crypto_info_t *crypto_info, int is_tx, |
| unsigned char *iv, size_t ivlen, |
| unsigned char *key, size_t keylen, |
| unsigned char *mac_key, size_t mac_secret_size) |
| { |
| unsigned char geniv[EVP_GCM_TLS_EXPLICIT_IV_LEN]; |
| unsigned char *eiv = NULL; |
| |
| #ifdef OPENSSL_NO_KTLS_RX |
| if (!is_tx) |
| return 0; |
| #endif |
| |
| if (EVP_CIPHER_get_mode(c) == EVP_CIPH_GCM_MODE |
| || EVP_CIPHER_get_mode(c) == EVP_CIPH_CCM_MODE) { |
| if (!ossl_assert(EVP_GCM_TLS_FIXED_IV_LEN == EVP_CCM_TLS_FIXED_IV_LEN) |
| || !ossl_assert(EVP_GCM_TLS_EXPLICIT_IV_LEN |
| == EVP_CCM_TLS_EXPLICIT_IV_LEN)) |
| return 0; |
| if (version == TLS1_2_VERSION) { |
| if (!ossl_assert(ivlen == EVP_GCM_TLS_FIXED_IV_LEN)) |
| return 0; |
| if (is_tx) { |
| if (RAND_bytes_ex(libctx, geniv, |
| EVP_GCM_TLS_EXPLICIT_IV_LEN, 0) |
| <= 0) |
| return 0; |
| } else { |
| memset(geniv, 0, EVP_GCM_TLS_EXPLICIT_IV_LEN); |
| } |
| eiv = geniv; |
| } else { |
| if (!ossl_assert(ivlen == EVP_GCM_TLS_FIXED_IV_LEN + EVP_GCM_TLS_EXPLICIT_IV_LEN)) |
| return 0; |
| eiv = iv + TLS_CIPHER_AES_GCM_128_SALT_SIZE; |
| } |
| } |
| |
| memset(crypto_info, 0, sizeof(*crypto_info)); |
| switch (EVP_CIPHER_get_nid(c)) { |
| #ifdef OPENSSL_KTLS_AES_GCM_128 |
| case NID_aes_128_gcm: |
| if (!ossl_assert(TLS_CIPHER_AES_GCM_128_SALT_SIZE |
| == EVP_GCM_TLS_FIXED_IV_LEN) |
| || !ossl_assert(TLS_CIPHER_AES_GCM_128_IV_SIZE |
| == EVP_GCM_TLS_EXPLICIT_IV_LEN)) |
| return 0; |
| crypto_info->gcm128.info.cipher_type = TLS_CIPHER_AES_GCM_128; |
| crypto_info->gcm128.info.version = version; |
| crypto_info->tls_crypto_info_len = sizeof(crypto_info->gcm128); |
| memcpy(crypto_info->gcm128.iv, eiv, TLS_CIPHER_AES_GCM_128_IV_SIZE); |
| memcpy(crypto_info->gcm128.salt, iv, TLS_CIPHER_AES_GCM_128_SALT_SIZE); |
| memcpy(crypto_info->gcm128.key, key, keylen); |
| memcpy(crypto_info->gcm128.rec_seq, rl_sequence, |
| TLS_CIPHER_AES_GCM_128_REC_SEQ_SIZE); |
| return 1; |
| #endif |
| #ifdef OPENSSL_KTLS_AES_GCM_256 |
| case NID_aes_256_gcm: |
| if (!ossl_assert(TLS_CIPHER_AES_GCM_256_SALT_SIZE |
| == EVP_GCM_TLS_FIXED_IV_LEN) |
| || !ossl_assert(TLS_CIPHER_AES_GCM_256_IV_SIZE |
| == EVP_GCM_TLS_EXPLICIT_IV_LEN)) |
| return 0; |
| crypto_info->gcm256.info.cipher_type = TLS_CIPHER_AES_GCM_256; |
| crypto_info->gcm256.info.version = version; |
| crypto_info->tls_crypto_info_len = sizeof(crypto_info->gcm256); |
| memcpy(crypto_info->gcm256.iv, eiv, TLS_CIPHER_AES_GCM_256_IV_SIZE); |
| memcpy(crypto_info->gcm256.salt, iv, TLS_CIPHER_AES_GCM_256_SALT_SIZE); |
| memcpy(crypto_info->gcm256.key, key, keylen); |
| memcpy(crypto_info->gcm256.rec_seq, rl_sequence, |
| TLS_CIPHER_AES_GCM_256_REC_SEQ_SIZE); |
| |
| return 1; |
| #endif |
| #ifdef OPENSSL_KTLS_AES_CCM_128 |
| case NID_aes_128_ccm: |
| if (!ossl_assert(TLS_CIPHER_AES_CCM_128_SALT_SIZE |
| == EVP_CCM_TLS_FIXED_IV_LEN) |
| || !ossl_assert(TLS_CIPHER_AES_CCM_128_IV_SIZE |
| == EVP_CCM_TLS_EXPLICIT_IV_LEN)) |
| return 0; |
| crypto_info->ccm128.info.cipher_type = TLS_CIPHER_AES_CCM_128; |
| crypto_info->ccm128.info.version = version; |
| crypto_info->tls_crypto_info_len = sizeof(crypto_info->ccm128); |
| memcpy(crypto_info->ccm128.iv, eiv, TLS_CIPHER_AES_CCM_128_IV_SIZE); |
| memcpy(crypto_info->ccm128.salt, iv, TLS_CIPHER_AES_CCM_128_SALT_SIZE); |
| memcpy(crypto_info->ccm128.key, key, keylen); |
| memcpy(crypto_info->ccm128.rec_seq, rl_sequence, |
| TLS_CIPHER_AES_CCM_128_REC_SEQ_SIZE); |
| return 1; |
| #endif |
| #ifdef OPENSSL_KTLS_CHACHA20_POLY1305 |
| case NID_chacha20_poly1305: |
| if (!ossl_assert(ivlen == TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE)) |
| return 0; |
| crypto_info->chacha20poly1305.info.cipher_type |
| = TLS_CIPHER_CHACHA20_POLY1305; |
| crypto_info->chacha20poly1305.info.version = version; |
| crypto_info->tls_crypto_info_len = sizeof(crypto_info->chacha20poly1305); |
| memcpy(crypto_info->chacha20poly1305.iv, iv, ivlen); |
| memcpy(crypto_info->chacha20poly1305.key, key, keylen); |
| memcpy(crypto_info->chacha20poly1305.rec_seq, rl_sequence, |
| TLS_CIPHER_CHACHA20_POLY1305_REC_SEQ_SIZE); |
| return 1; |
| #endif |
| default: |
| return 0; |
| } |
| } |
| |
| #endif /* OPENSSL_SYS_LINUX */ |
| |
| static int ktls_set_crypto_state(OSSL_RECORD_LAYER *rl, int level, |
| unsigned char *key, size_t keylen, |
| unsigned char *iv, size_t ivlen, |
| unsigned char *mackey, size_t mackeylen, |
| const EVP_CIPHER *ciph, |
| size_t taglen, |
| int mactype, |
| const EVP_MD *md, |
| COMP_METHOD *comp) |
| { |
| ktls_crypto_info_t crypto_info; |
| |
| /* |
| * Check if we are suitable for KTLS. If not suitable we return |
| * OSSL_RECORD_RETURN_NON_FATAL_ERR so that other record layers can be tried |
| * instead |
| */ |
| |
| if (comp != NULL) |
| return OSSL_RECORD_RETURN_NON_FATAL_ERR; |
| |
| /* ktls supports only the maximum fragment size */ |
| if (rl->max_frag_len != SSL3_RT_MAX_PLAIN_LENGTH) |
| return OSSL_RECORD_RETURN_NON_FATAL_ERR; |
| |
| /* check that cipher is supported */ |
| if (!ktls_int_check_supported_cipher(rl, ciph, md, taglen)) |
| return OSSL_RECORD_RETURN_NON_FATAL_ERR; |
| |
| /* All future data will get encrypted by ktls. Flush the BIO or skip ktls */ |
| if (rl->direction == OSSL_RECORD_DIRECTION_WRITE) { |
| if (BIO_flush(rl->bio) <= 0) |
| return OSSL_RECORD_RETURN_NON_FATAL_ERR; |
| |
| /* KTLS does not support record padding */ |
| if (rl->padding != NULL || rl->block_padding > 0) |
| return OSSL_RECORD_RETURN_NON_FATAL_ERR; |
| } |
| |
| if (!ktls_configure_crypto(rl->libctx, rl->version, ciph, md, rl->sequence, |
| &crypto_info, |
| rl->direction == OSSL_RECORD_DIRECTION_WRITE, |
| iv, ivlen, key, keylen, mackey, mackeylen)) |
| return OSSL_RECORD_RETURN_NON_FATAL_ERR; |
| |
| if (!BIO_set_ktls(rl->bio, &crypto_info, rl->direction)) |
| return OSSL_RECORD_RETURN_NON_FATAL_ERR; |
| |
| if (rl->direction == OSSL_RECORD_DIRECTION_WRITE && (rl->options & SSL_OP_ENABLE_KTLS_TX_ZEROCOPY_SENDFILE) != 0) |
| /* Ignore errors. The application opts in to using the zerocopy |
| * optimization. If the running kernel doesn't support it, just |
| * continue without the optimization. |
| */ |
| BIO_set_ktls_tx_zerocopy_sendfile(rl->bio); |
| |
| return OSSL_RECORD_RETURN_SUCCESS; |
| } |
| |
| static int ktls_read_n(OSSL_RECORD_LAYER *rl, size_t n, size_t max, int extend, |
| int clearold, size_t *readbytes) |
| { |
| int ret; |
| |
| ret = tls_default_read_n(rl, n, max, extend, clearold, readbytes); |
| |
| if (ret < OSSL_RECORD_RETURN_RETRY) { |
| switch (errno) { |
| case EBADMSG: |
| RLAYERfatal(rl, SSL_AD_BAD_RECORD_MAC, |
| SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC); |
| break; |
| case EMSGSIZE: |
| RLAYERfatal(rl, SSL_AD_RECORD_OVERFLOW, |
| SSL_R_PACKET_LENGTH_TOO_LONG); |
| break; |
| case EINVAL: |
| RLAYERfatal(rl, SSL_AD_PROTOCOL_VERSION, |
| SSL_R_WRONG_VERSION_NUMBER); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int ktls_cipher(OSSL_RECORD_LAYER *rl, TLS_RL_RECORD *inrecs, |
| size_t n_recs, int sending, SSL_MAC_BUF *mac, |
| size_t macsize) |
| { |
| return 1; |
| } |
| |
| static int ktls_validate_record_header(OSSL_RECORD_LAYER *rl, TLS_RL_RECORD *rec) |
| { |
| if (rec->rec_version != TLS1_2_VERSION) { |
| RLAYERfatal(rl, SSL_AD_DECODE_ERROR, SSL_R_WRONG_VERSION_NUMBER); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int ktls_post_process_record(OSSL_RECORD_LAYER *rl, TLS_RL_RECORD *rec) |
| { |
| if (rl->version == TLS1_3_VERSION) |
| return tls13_common_post_process_record(rl, rec); |
| |
| return 1; |
| } |
| |
| static int |
| ktls_new_record_layer(OSSL_LIB_CTX *libctx, const char *propq, int vers, |
| int role, int direction, int level, uint16_t epoch, |
| unsigned char *secret, size_t secretlen, |
| unsigned char *key, size_t keylen, unsigned char *iv, |
| size_t ivlen, unsigned char *mackey, size_t mackeylen, |
| const EVP_CIPHER *ciph, size_t taglen, |
| int mactype, |
| const EVP_MD *md, COMP_METHOD *comp, |
| const EVP_MD *kdfdigest, BIO *prev, BIO *transport, |
| BIO *next, BIO_ADDR *local, BIO_ADDR *peer, |
| const OSSL_PARAM *settings, const OSSL_PARAM *options, |
| const OSSL_DISPATCH *fns, void *cbarg, void *rlarg, |
| OSSL_RECORD_LAYER **retrl) |
| { |
| int ret; |
| |
| ret = tls_int_new_record_layer(libctx, propq, vers, role, direction, level, |
| ciph, taglen, md, comp, prev, |
| transport, next, settings, |
| options, fns, cbarg, retrl); |
| |
| if (ret != OSSL_RECORD_RETURN_SUCCESS) |
| return ret; |
| |
| (*retrl)->funcs = &ossl_ktls_funcs; |
| |
| ret = (*retrl)->funcs->set_crypto_state(*retrl, level, key, keylen, iv, |
| ivlen, mackey, mackeylen, ciph, |
| taglen, mactype, md, comp); |
| |
| if (ret != OSSL_RECORD_RETURN_SUCCESS) { |
| tls_free(*retrl); |
| *retrl = NULL; |
| } else { |
| /* |
| * With KTLS we always try and read as much as possible and fill the |
| * buffer |
| */ |
| (*retrl)->read_ahead = 1; |
| } |
| return ret; |
| } |
| |
| static int ktls_allocate_write_buffers(OSSL_RECORD_LAYER *rl, |
| OSSL_RECORD_TEMPLATE *templates, |
| size_t numtempl, size_t *prefix) |
| { |
| if (!ossl_assert(numtempl == 1)) |
| return 0; |
| |
| /* |
| * We just use the end application buffer in the case of KTLS, so nothing |
| * to do. We pretend we set up one buffer. |
| */ |
| rl->numwpipes = 1; |
| |
| return 1; |
| } |
| |
| static int ktls_initialise_write_packets(OSSL_RECORD_LAYER *rl, |
| OSSL_RECORD_TEMPLATE *templates, |
| size_t numtempl, |
| OSSL_RECORD_TEMPLATE *prefixtempl, |
| WPACKET *pkt, |
| TLS_BUFFER *bufs, |
| size_t *wpinited) |
| { |
| TLS_BUFFER *wb; |
| |
| /* |
| * We just use the application buffer directly and don't use any WPACKET |
| * structures |
| */ |
| wb = &bufs[0]; |
| wb->type = templates[0].type; |
| |
| /* |
| * Free any internal buffer allocated during a previous write retry |
| * (see tls_retry_write_records). App buffers are not ours to free. |
| */ |
| if (!TLS_BUFFER_is_app_buffer(wb)) |
| OPENSSL_free(TLS_BUFFER_get_buf(wb)); |
| |
| /* |
| * ktls doesn't modify the buffer, but to avoid a warning we need |
| * to discard the const qualifier. |
| */ |
| TLS_BUFFER_set_buf(wb, (unsigned char *)templates[0].buf); |
| TLS_BUFFER_set_offset(wb, 0); |
| TLS_BUFFER_set_app_buffer(wb, 1); |
| |
| return 1; |
| } |
| |
| static int ktls_prepare_record_header(OSSL_RECORD_LAYER *rl, |
| WPACKET *thispkt, |
| OSSL_RECORD_TEMPLATE *templ, |
| uint8_t rectype, |
| unsigned char **recdata) |
| { |
| /* The kernel writes the record header, so nothing to do */ |
| *recdata = NULL; |
| |
| return 1; |
| } |
| |
| static int ktls_prepare_for_encryption(OSSL_RECORD_LAYER *rl, |
| size_t mac_size, |
| WPACKET *thispkt, |
| TLS_RL_RECORD *thiswr) |
| { |
| /* No encryption, so nothing to do */ |
| return 1; |
| } |
| |
| static int ktls_post_encryption_processing(OSSL_RECORD_LAYER *rl, |
| size_t mac_size, |
| OSSL_RECORD_TEMPLATE *templ, |
| WPACKET *thispkt, |
| TLS_RL_RECORD *thiswr) |
| { |
| /* The kernel does anything that is needed, so nothing to do here */ |
| return 1; |
| } |
| |
| static int ktls_prepare_write_bio(OSSL_RECORD_LAYER *rl, int type) |
| { |
| /* |
| * To prevent coalescing of control and data messages, |
| * such as in buffer_write, we flush the BIO |
| */ |
| if (type != SSL3_RT_APPLICATION_DATA) { |
| int ret, i = BIO_flush(rl->bio); |
| |
| if (i <= 0) { |
| if (BIO_should_retry(rl->bio)) |
| ret = OSSL_RECORD_RETURN_RETRY; |
| else |
| ret = OSSL_RECORD_RETURN_FATAL; |
| return ret; |
| } |
| BIO_set_ktls_ctrl_msg(rl->bio, type); |
| } |
| |
| return OSSL_RECORD_RETURN_SUCCESS; |
| } |
| |
| static int ktls_alloc_buffers(OSSL_RECORD_LAYER *rl) |
| { |
| /* We use the application buffer directly for writing */ |
| if (rl->direction == OSSL_RECORD_DIRECTION_WRITE) |
| return 1; |
| |
| return tls_alloc_buffers(rl); |
| } |
| |
| static struct record_functions_st ossl_ktls_funcs = { |
| ktls_set_crypto_state, |
| ktls_cipher, |
| NULL, |
| tls_default_set_protocol_version, |
| ktls_read_n, |
| tls_get_more_records, |
| ktls_validate_record_header, |
| ktls_post_process_record, |
| tls_get_max_records_default, |
| tls_write_records_default, |
| ktls_allocate_write_buffers, |
| ktls_initialise_write_packets, |
| NULL, |
| ktls_prepare_record_header, |
| NULL, |
| ktls_prepare_for_encryption, |
| ktls_post_encryption_processing, |
| ktls_prepare_write_bio |
| }; |
| |
| const OSSL_RECORD_METHOD ossl_ktls_record_method = { |
| ktls_new_record_layer, |
| tls_free, |
| tls_unprocessed_read_pending, |
| tls_processed_read_pending, |
| tls_app_data_pending, |
| tls_get_max_records, |
| tls_write_records, |
| tls_retry_write_records, |
| tls_read_record, |
| tls_release_record, |
| tls_get_alert_code, |
| tls_set1_bio, |
| tls_set_protocol_version, |
| tls_set_plain_alerts, |
| tls_set_first_handshake, |
| tls_set_max_pipelines, |
| NULL, |
| tls_get_state, |
| tls_set_options, |
| tls_get_compression, |
| tls_set_max_frag_len, |
| NULL, |
| tls_increment_sequence_ctr, |
| ktls_alloc_buffers, |
| tls_free_buffers |
| }; |