USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #define __STDC_WANT_LIB_EXT1__ 1 #include <string.h> #include <openssl/hmac.h> #include <freeradius-devel/util/sha1.h> #include <freeradius-devel/tls/base.h> #include <freeradius-devel/tls/missing.h> #include "tls.h" #include "base.h" #include "attrs.h" #define EAP_TLS_MPPE_KEY_LEN 32 /** Generate keys according to RFC 2716 and add to the reply * */ int eap_crypto_mppe_keys(REQUEST *request, SSL *ssl, char const *prf_label, size_t prf_label_len) { uint8_t out[4 * EAP_TLS_MPPE_KEY_LEN]; uint8_t *p; if (SSL_export_keying_material(ssl, out, sizeof(out), prf_label, prf_label_len, NULL, 0, 0) != 1) { tls_log_error(request, "Failed generating MPPE keys"); return -1; } if (RDEBUG_ENABLED3) { uint8_t random[SSL3_RANDOM_SIZE]; size_t random_len; uint8_t master_key[SSL_MAX_MASTER_KEY_LENGTH]; size_t master_key_len; RDEBUG3("Key Derivation Function input"); RINDENT(); RDEBUG3("prf label : %pV", fr_box_strvalue_len(prf_label, prf_label_len)); master_key_len = SSL_SESSION_get_master_key(SSL_get_session(ssl), master_key, sizeof(master_key)); RDEBUG3("master session key : %pH", fr_box_octets(master_key, master_key_len)); random_len = SSL_get_client_random(ssl, random, SSL3_RANDOM_SIZE); RDEBUG3("client random : %pH", fr_box_octets(random, random_len)); random_len = SSL_get_server_random(ssl, random, SSL3_RANDOM_SIZE); RDEBUG3("server random : %pH", fr_box_octets(random, random_len)); REXDENT(); } RDEBUG2("Adding session keys"); p = out; eap_add_reply(request, attr_ms_mppe_recv_key, p, EAP_TLS_MPPE_KEY_LEN); p += EAP_TLS_MPPE_KEY_LEN; eap_add_reply(request, attr_ms_mppe_send_key, p, EAP_TLS_MPPE_KEY_LEN); eap_add_reply(request, attr_eap_msk, out, 64); eap_add_reply(request, attr_eap_emsk, out + 64, 64); return 0; }
int eap_crypto_tls_session_id(TALLOC_CTX *ctx, #if OPENSSL_VERSION_NUMBER < 0x10101000L UNUSED #endif REQUEST *request, SSL *ssl, uint8_t **out, uint8_t eap_type, #if OPENSSL_VERSION_NUMBER < 0x10100000L UNUSED #endif char const *prf_label, #if OPENSSL_VERSION_NUMBER < 0x10101000L UNUSED #endif size_t prf_label_len) { uint8_t *buff = NULL, *p; *out = NULL; #if OPENSSL_VERSION_NUMBER >= 0x10100000L if (!prf_label) goto random_based_session_id; switch (SSL_SESSION_get_protocol_version(SSL_get_session(ssl))) { case SSL2_VERSION: /* Should never happen */ case SSL3_VERSION: /* Should never happen */ return - 1; case TLS1_VERSION: /* No Method ID */ case TLS1_1_VERSION: /* No Method ID */ case TLS1_2_VERSION: /* No Method ID */ random_based_session_id: #endif MEM(buff = p = talloc_array(ctx, uint8_t, sizeof(eap_type) + (2 * SSL3_RANDOM_SIZE))); *p++ = eap_type; SSL_get_client_random(ssl, p, SSL3_RANDOM_SIZE); p += SSL3_RANDOM_SIZE; SSL_get_server_random(ssl, p, SSL3_RANDOM_SIZE); #if OPENSSL_VERSION_NUMBER >= 0x10101000L break; /* * Session-Id = <EAP-Type> || Method-Id * Method-Id = TLS-Exporter("EXPORTER_EAP_TLS_Method-Id", "", 64) */ case TLS1_3_VERSION: default: { MEM(buff = p = talloc_array(ctx, uint8_t, sizeof(eap_type) + 64)); *p++ = eap_type; if (SSL_export_keying_material(ssl, p, 64, prf_label, prf_label_len, NULL, 0, 0) != 1) { tls_log_error(request, "Failed generating TLS session ID"); return -1; } } break; #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L } #endif *out = buff; return 0; }
/** Create a new server TLS session * * Configures a new server TLS session, configuring options, setting callbacks etc... * * @param ctx to alloc session data in. Should usually be NULL unless the lifetime of the * session is tied to another talloc'd object. * @param conf values for this TLS session. * @param request The current #REQUEST. * @param client_cert Whether to require a client_cert. * @return * - A new session on success. * - NULL on error. */ tls_session_t *tls_session_init_server(TALLOC_CTX *ctx, fr_tls_conf_t *conf, REQUEST *request, bool client_cert) { tls_session_t *session = NULL; SSL *new_tls = NULL; int verify_mode = 0; VALUE_PAIR *vp; SSL_CTX *ssl_ctx; rad_assert(request != NULL); rad_assert(conf->ctx_count > 0); RDEBUG2("Initiating new TLS session"); ssl_ctx = conf->ctx[(conf->ctx_count == 1) ? 0 : conf->ctx_next++ % conf->ctx_count]; /* mutex not needed */ rad_assert(ssl_ctx); new_tls = SSL_new(ssl_ctx); if (new_tls == NULL) { tls_log_error(request, "Error creating new TLS session"); return NULL; } session = talloc_zero(ctx, tls_session_t); if (session == NULL) { RERROR("Error allocating memory for TLS session"); SSL_free(new_tls); return NULL; } session_init(session); session->ctx = ssl_ctx; session->ssl = new_tls; talloc_set_destructor(session, _tls_session_free); /* * Initialize callbacks */ session->record_init = record_init; session->record_close = record_close; session->record_from_buff = record_from_buff; session->record_to_buff = record_to_buff; /* * Create & hook the BIOs to handle the dirty side of the * SSL. This is *very important* as we want to handle * the transmission part. Now the only IO interface * that SSL is aware of, is our defined BIO buffers. * * This means that all SSL IO is done to/from memory, * and we can update those BIOs from the packets we've * received. */ session->into_ssl = BIO_new(BIO_s_mem()); session->from_ssl = BIO_new(BIO_s_mem()); SSL_set_bio(session->ssl, session->into_ssl, session->from_ssl); /* * Add the message callback to identify what type of * message/handshake is passed */ SSL_set_msg_callback(new_tls, tls_session_msg_cb); SSL_set_msg_callback_arg(new_tls, session); SSL_set_info_callback(new_tls, tls_session_info_cb); /* * This sets the context sessions can be resumed in. * This is to prevent sessions being created by one application * and used by another. In our case it prevents sessions being * reused between modules, or TLS server components such as * RADSEC. * * A context must always be set when doing session resumption * otherwise session resumption will fail. * * As the context ID must be <= 32, we digest the context * data with sha256. */ rad_assert(conf->session_id_name); { char *context_id; EVP_MD_CTX *md_ctx; uint8_t digest[SHA256_DIGEST_LENGTH]; static_assert(sizeof(digest) <= SSL_MAX_SSL_SESSION_ID_LENGTH, "SSL_MAX_SSL_SESSION_ID_LENGTH must be >= SHA256_DIGEST_LENGTH"); if (tmpl_aexpand(session, &context_id, request, conf->session_id_name, NULL, NULL) < 0) { RPEDEBUG("Failed expanding session ID"); talloc_free(session); } MEM(md_ctx = EVP_MD_CTX_create()); EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL); EVP_DigestUpdate(md_ctx, context_id, talloc_array_length(context_id) - 1); EVP_DigestFinal_ex(md_ctx, digest, NULL); EVP_MD_CTX_destroy(md_ctx); talloc_free(context_id); if (!fr_cond_assert(SSL_set_session_id_context(session->ssl, digest, sizeof(digest)) == 1)) { talloc_free(session); return NULL; } } /* * Add the session certificate to the session. */ vp = fr_pair_find_by_da(request->control, attr_tls_session_cert_file, TAG_ANY); if (vp) { RDEBUG2("Loading TLS session certificate \"%s\"", vp->vp_strvalue); if (SSL_use_certificate_file(session->ssl, vp->vp_strvalue, SSL_FILETYPE_PEM) != 1) { tls_log_error(request, "Failed loading TLS session certificate \"%s\"", vp->vp_strvalue); talloc_free(session); return NULL; } if (SSL_use_PrivateKey_file(session->ssl, vp->vp_strvalue, SSL_FILETYPE_PEM) != 1) { tls_log_error(request, "Failed loading TLS session certificate \"%s\"", vp->vp_strvalue); talloc_free(session); return NULL; } if (SSL_check_private_key(session->ssl) != 1) { tls_log_error(request, "Failed validating TLS session certificate \"%s\"", vp->vp_strvalue); talloc_free(session); return NULL; } /* * Better to perform explicit checks, than rely * on OpenSSL's opaque error messages. */ } else { if (!conf->chains || !conf->chains[0]->private_key_file) { ERROR("TLS Server requires a private key file"); talloc_free(session); return NULL; } if (!conf->chains || !conf->chains[0]->certificate_file) { ERROR("TLS Server requires a certificate file"); talloc_free(session); return NULL; } } /* * In Server mode we only accept. * * This sets up the SSL session to work correctly with * tls_session_handhsake. */ SSL_set_accept_state(session->ssl); /* * Verify the peer certificate, if asked. */ if (client_cert) { RDEBUG2("Setting verify mode to require certificate from client"); verify_mode = SSL_VERIFY_PEER; verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; verify_mode |= SSL_VERIFY_CLIENT_ONCE; } SSL_set_verify(session->ssl, verify_mode, tls_validate_cert_cb); SSL_set_ex_data(session->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf); SSL_set_ex_data(session->ssl, FR_TLS_EX_INDEX_TLS_SESSION, (void *)session); /* * We use default fragment size, unless the Framed-MTU * tells us it's too big. Note that we do NOT account * for the EAP-TLS headers if conf->fragment_size is * large, because that config item looks to be confusing. * * i.e. it should REALLY be called MTU, and the code here * should figure out what that means for TLS fragment size. * asking the administrator to know the internal details * of EAP-TLS in order to calculate fragment sizes is * just too much. */ session->mtu = conf->fragment_size; vp = fr_pair_find_by_da(request->packet->vps, attr_framed_mtu, TAG_ANY); if (vp && (vp->vp_uint32 > 100) && (vp->vp_uint32 < session->mtu)) { RDEBUG2("Setting fragment_len to %u from &Framed-MTU", vp->vp_uint32); session->mtu = vp->vp_uint32; } if (conf->session_cache_server) session->allow_session_resumption = true; /* otherwise it's false */ return session; }
/** Continue a TLS handshake * * Advance the TLS handshake by feeding OpenSSL data from dirty_in, * and reading data from OpenSSL into dirty_out. * * @param request The current request. * @param session The current TLS session. * @return * - 0 on error. * - 1 on success. */ int tls_session_handshake(REQUEST *request, tls_session_t *session) { int ret; /* * This is a logic error. tls_session_handshake * must not be called if the handshake is * complete tls_session_recv must be * called instead. */ if (SSL_is_init_finished(session->ssl)) { REDEBUG("Attempted to continue TLS handshake, but handshake has completed"); return 0; } if (session->invalid) { REDEBUG("Preventing invalid session from continuing"); return 0; } /* * Feed dirty data into OpenSSL, so that is can either * process it as Application data (decrypting it) * or continue the TLS handshake. */ if (session->dirty_in.used) { ret = BIO_write(session->into_ssl, session->dirty_in.data, session->dirty_in.used); if (ret != (int)session->dirty_in.used) { REDEBUG("Failed writing %zd bytes to TLS BIO: %d", session->dirty_in.used, ret); record_init(&session->dirty_in); return 0; } record_init(&session->dirty_in); } /* * Magic/More magic? Although SSL_read is normally * used to read application data, it will also * continue the TLS handshake. Removing this call will * cause the handshake to fail. * * We don't ever expect to actually *receive* application * data here. * * The reason why we call SSL_read instead of SSL_accept, * or SSL_connect, as it allows this function * to be used, irrespective or whether we're acting * as a client or a server. * * If acting as a client SSL_set_connect_state must have * been called before this function. * * If acting as a server SSL_set_accept_state must have * been called before this function. */ ret = SSL_read(session->ssl, session->clean_out.data + session->clean_out.used, sizeof(session->clean_out.data) - session->clean_out.used); if (ret > 0) { session->clean_out.used += ret; return 1; } if (!tls_log_io_error(request, session, ret, "Failed in SSL_read")) return 0; /* * This only occurs once per session, where calling * SSL_read updates the state of the SSL session, setting * this flag to true. * * Callbacks provide enough info so we don't need to * print debug statements when the handshake is in other * states. */ if (SSL_is_init_finished(session->ssl)) { SSL_CIPHER const *cipher; VALUE_PAIR *vp; char const *str_version; char cipher_desc[256], cipher_desc_clean[256]; char *p = cipher_desc, *q = cipher_desc_clean; cipher = SSL_get_current_cipher(session->ssl); SSL_CIPHER_description(cipher, cipher_desc, sizeof(cipher_desc)); /* * Cleanup the output from OpenSSL * Seems to print info in a tabular format. */ while (*p != '\0') { if (isspace(*p)) { *q++ = *p; while (isspace(*++p)); continue; } *q++ = *p++; } *q = '\0'; RDEBUG2("Cipher suite: %s", cipher_desc_clean); vp = fr_pair_afrom_num(request->state_ctx, 0, FR_TLS_SESSION_CIPHER_SUITE); if (vp) { fr_pair_value_strcpy(vp, SSL_CIPHER_get_name(cipher)); fr_pair_add(&request->state, vp); RDEBUG2(" &session-state:TLS-Session-Cipher-Suite := \"%s\"", vp->vp_strvalue); } switch (session->info.version) { case SSL2_VERSION: str_version = "SSL 2.0"; break; case SSL3_VERSION: str_version = "SSL 3.0"; break; case TLS1_VERSION: str_version = "TLS 1.0"; break; #ifdef TLS1_1_VERSION case TLS1_1_VERSION: str_version = "TLS 1.1"; break; #endif #ifdef TLS1_2_VERSION case TLS1_2_VERSION: str_version = "TLS 1.2"; break; #endif #ifdef TLS1_3_VERSON case TLS1_3_VERSION: str_version = "TLS 1.3"; break; #endif default: str_version = "UNKNOWN"; break; } vp = fr_pair_afrom_num(request->state_ctx, 0, FR_TLS_SESSION_VERSION); if (vp) { fr_pair_value_strcpy(vp, str_version); fr_pair_add(&request->state, vp); RDEBUG2(" &session-state:TLS-Session-Version := \"%s\"", str_version); } #if OPENSSL_VERSION_NUMBER >= 0x10001000L /* * Cache the SSL_SESSION pointer. * * Which contains all the data we need for session resumption. */ if (!session->ssl_session) { session->ssl_session = SSL_get_session(session->ssl); if (!session->ssl_session) { REDEBUG("Failed getting TLS session"); return 0; } } if (RDEBUG_ENABLED3) { BIO *ssl_log = BIO_new(BIO_s_mem()); if (ssl_log) { if (SSL_SESSION_print(ssl_log, session->ssl_session) == 1) { SSL_DRAIN_ERROR_QUEUE(RDEBUG3, "", ssl_log); } else { RDEBUG3("Failed retrieving session data"); } BIO_free(ssl_log); } } #endif /* * Session was resumed, add attribute to mark it as such. */ if (SSL_session_reused(session->ssl)) { /* * Mark the request as resumed. */ MEM(pair_update_request(&vp, attr_eap_session_resumed) >= 0); vp->vp_bool = true; } } /* * Get data to pack and send back to the TLS peer. */ ret = BIO_ctrl_pending(session->from_ssl); if (ret > 0) { ret = BIO_read(session->from_ssl, session->dirty_out.data, sizeof(session->dirty_out.data)); if (ret > 0) { session->dirty_out.used = ret; } else if (BIO_should_retry(session->from_ssl)) { record_init(&session->dirty_in); RDEBUG2("Asking for more data in tunnel"); return 1; } else { tls_log_error(NULL, NULL); record_init(&session->dirty_in); return 0; } } else { /* Its clean application data, do whatever we want */ record_init(&session->clean_out); } /* * W would prefer to latch on info.content_type but * (I think its...) tls_session_msg_cb() updates it * after the call to tls_session_handshake_alert() */ if (session->handshake_alert.level) { /* * FIXME RFC 4851 section 3.6.1 - peer might ACK alert and include a restarted ClientHello * which eap_tls_session_status() will fail on */ session->info.content_type = SSL3_RT_ALERT; session->dirty_out.data[0] = session->info.content_type; session->dirty_out.data[1] = 3; session->dirty_out.data[2] = 1; session->dirty_out.data[3] = 0; session->dirty_out.data[4] = 2; session->dirty_out.data[5] = session->handshake_alert.level; session->dirty_out.data[6] = session->handshake_alert.description; session->dirty_out.used = 7; session->handshake_alert.level = 0; } /* We are done with dirty_in, reinitialize it */ record_init(&session->dirty_in); return 1; }