/* * Copyright (C) 2001-2012 Free Software Foundation, Inc. * * Author: Nikos Mavrogiannopoulos * * This file is part of GnuTLS. * * The GnuTLS is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see * */ /* Functions that are supposed to run after the handshake procedure is * finished. These functions activate the established security parameters. */ #include #include #include #include #include #include #include #include #include #include static int _gnutls_set_kx(gnutls_session_t session, gnutls_kx_algorithm_t algo); static const char keyexp[] = "key expansion"; static const int keyexp_length = sizeof(keyexp) - 1; static const char ivblock[] = "IV block"; static const int ivblock_length = sizeof(ivblock) - 1; static const char cliwrite[] = "client write key"; static const int cliwrite_length = sizeof(cliwrite) - 1; static const char servwrite[] = "server write key"; static const int servwrite_length = sizeof(servwrite) - 1; /* This function is to be called after handshake, when master_secret, * client_random and server_random have been initialized. * This function creates the keys and stores them into pending session. * (session->cipher_specs) */ static int _gnutls_set_keys(gnutls_session_t session, record_parameters_st * params, int hash_size, int IV_size, int key_size) { /* FIXME: This function is too long */ uint8_t rnd[2 * GNUTLS_RANDOM_SIZE]; uint8_t rrnd[2 * GNUTLS_RANDOM_SIZE]; int pos, ret; int block_size; char buf[65]; /* avoid using malloc */ uint8_t key_block[2 * MAX_HASH_SIZE + 2 * MAX_CIPHER_KEY_SIZE + 2 * MAX_CIPHER_BLOCK_SIZE]; record_state_st *client_write, *server_write; if (session->security_parameters.entity == GNUTLS_CLIENT) { client_write = ¶ms->write; server_write = ¶ms->read; } else { client_write = ¶ms->read; server_write = ¶ms->write; } block_size = 2 * hash_size + 2 * key_size; block_size += 2 * IV_size; memcpy(rnd, session->security_parameters.server_random, GNUTLS_RANDOM_SIZE); memcpy(&rnd[GNUTLS_RANDOM_SIZE], session->security_parameters.client_random, GNUTLS_RANDOM_SIZE); memcpy(rrnd, session->security_parameters.client_random, GNUTLS_RANDOM_SIZE); memcpy(&rrnd[GNUTLS_RANDOM_SIZE], session->security_parameters.server_random, GNUTLS_RANDOM_SIZE); if (get_num_version(session) == GNUTLS_SSL3) { /* SSL 3 */ ret = _gnutls_ssl3_generate_random (session->security_parameters.master_secret, GNUTLS_MASTER_SIZE, rnd, 2 * GNUTLS_RANDOM_SIZE, block_size, key_block); } else { /* TLS 1.0 */ ret = _gnutls_PRF(session, session->security_parameters.master_secret, GNUTLS_MASTER_SIZE, keyexp, keyexp_length, rnd, 2 * GNUTLS_RANDOM_SIZE, block_size, key_block); } if (ret < 0) return gnutls_assert_val(ret); _gnutls_hard_log("INT: KEY BLOCK[%d]: %s\n", block_size, _gnutls_bin2hex(key_block, block_size, buf, sizeof(buf), NULL)); pos = 0; if (hash_size > 0) { if (_gnutls_set_datum (&client_write->mac_secret, &key_block[pos], hash_size) < 0) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); pos += hash_size; if (_gnutls_set_datum (&server_write->mac_secret, &key_block[pos], hash_size) < 0) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); pos += hash_size; } if (key_size > 0) { uint8_t *client_write_key, *server_write_key; int client_write_key_size, server_write_key_size; client_write_key = &key_block[pos]; client_write_key_size = key_size; pos += key_size; server_write_key = &key_block[pos]; server_write_key_size = key_size; pos += key_size; if (_gnutls_set_datum (&client_write->key, client_write_key, client_write_key_size) < 0) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); _gnutls_hard_log("INT: CLIENT WRITE KEY [%d]: %s\n", client_write_key_size, _gnutls_bin2hex(client_write_key, client_write_key_size, buf, sizeof(buf), NULL)); if (_gnutls_set_datum (&server_write->key, server_write_key, server_write_key_size) < 0) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); _gnutls_hard_log("INT: SERVER WRITE KEY [%d]: %s\n", server_write_key_size, _gnutls_bin2hex(server_write_key, server_write_key_size, buf, sizeof(buf), NULL)); } /* IV generation in export and non export ciphers. */ if (IV_size > 0) { if (_gnutls_set_datum (&client_write->IV, &key_block[pos], IV_size) < 0) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); pos += IV_size; if (_gnutls_set_datum (&server_write->IV, &key_block[pos], IV_size) < 0) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); } return 0; } static int _gnutls_init_record_state(record_parameters_st * params, const version_entry_st * ver, int read, record_state_st * state) { int ret; gnutls_datum_t *iv = NULL; if (!_gnutls_version_has_explicit_iv(ver)) { if (_gnutls_cipher_is_block(params->cipher) != CIPHER_STREAM) iv = &state->IV; } ret = _gnutls_auth_cipher_init(&state->cipher_state, params->cipher, &state->key, iv, params->mac, &state->mac_secret, (ver->id == GNUTLS_SSL3) ? 1 : 0, 1 - read /*1==encrypt */ ); if (ret < 0 && params->cipher->id != GNUTLS_CIPHER_NULL) return gnutls_assert_val(ret); ret = _gnutls_comp_init(&state->compression_state, params->compression_algorithm, read /*1==decompress */ ); if (ret < 0) return gnutls_assert_val(ret); return 0; } int _gnutls_epoch_set_cipher_suite(gnutls_session_t session, int epoch_rel, const uint8_t suite[2]) { const cipher_entry_st *cipher_algo; const mac_entry_st *mac_algo; record_parameters_st *params; int ret; ret = _gnutls_epoch_get(session, epoch_rel, ¶ms); if (ret < 0) return gnutls_assert_val(ret); if (params->initialized || params->cipher != NULL || params->mac != NULL) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); cipher_algo = _gnutls_cipher_suite_get_cipher_algo(suite); mac_algo = _gnutls_cipher_suite_get_mac_algo(suite); if (_gnutls_cipher_is_ok(cipher_algo) == 0 || _gnutls_mac_is_ok(mac_algo) == 0) return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); if (_gnutls_cipher_priority(session, cipher_algo->id) < 0) return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); if (_gnutls_mac_priority(session, mac_algo->id) < 0) return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); params->cipher = cipher_algo; params->mac = mac_algo; return 0; } int _gnutls_epoch_set_compression(gnutls_session_t session, int epoch_rel, gnutls_compression_method_t comp_algo) { record_parameters_st *params; int ret; ret = _gnutls_epoch_get(session, epoch_rel, ¶ms); if (ret < 0) return gnutls_assert_val(ret); if (params->initialized || params->compression_algorithm != GNUTLS_COMP_UNKNOWN) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); if (_gnutls_compression_is_ok(comp_algo) != 0) return gnutls_assert_val (GNUTLS_E_UNKNOWN_COMPRESSION_ALGORITHM); params->compression_algorithm = comp_algo; return 0; } void _gnutls_epoch_set_null_algos(gnutls_session_t session, record_parameters_st * params) { /* This is only called on startup. We are extra paranoid about this because it may cause unencrypted application data to go out on the wire. */ if (params->initialized || params->epoch != 0) { gnutls_assert(); return; } params->cipher = cipher_to_entry(GNUTLS_CIPHER_NULL); params->mac = mac_to_entry(GNUTLS_MAC_NULL); params->compression_algorithm = GNUTLS_COMP_NULL; params->initialized = 1; } int _gnutls_epoch_set_keys(gnutls_session_t session, uint16_t epoch) { int hash_size; int IV_size; int key_size; gnutls_compression_method_t comp_algo; record_parameters_st *params; int ret; const version_entry_st *ver = get_version(session); if (unlikely(ver == NULL)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); ret = _gnutls_epoch_get(session, epoch, ¶ms); if (ret < 0) return gnutls_assert_val(ret); if (params->initialized) return 0; _gnutls_record_log ("REC[%p]: Initializing epoch #%u\n", session, params->epoch); comp_algo = params->compression_algorithm; if (_gnutls_cipher_is_ok(params->cipher) == 0 || _gnutls_mac_is_ok(params->mac) == 0) return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); if (_gnutls_cipher_priority(session, params->cipher->id) < 0) return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); if (_gnutls_mac_priority(session, params->mac->id) < 0) return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); if (_gnutls_compression_is_ok(comp_algo) != 0) return gnutls_assert_val (GNUTLS_E_UNKNOWN_COMPRESSION_ALGORITHM); if (!_gnutls_version_has_explicit_iv(ver)) { if (_gnutls_cipher_is_block(params->cipher) != CIPHER_STREAM) { IV_size = _gnutls_cipher_get_iv_size(params->cipher); } else IV_size = _gnutls_cipher_get_implicit_iv_size(params->cipher); } else IV_size = _gnutls_cipher_get_implicit_iv_size(params->cipher); key_size = _gnutls_cipher_get_key_size(params->cipher); hash_size = _gnutls_mac_get_key_size(params->mac); ret = _gnutls_set_keys (session, params, hash_size, IV_size, key_size); if (ret < 0) return gnutls_assert_val(ret); ret = _gnutls_init_record_state(params, ver, 1, ¶ms->read); if (ret < 0) return gnutls_assert_val(ret); ret = _gnutls_init_record_state(params, ver, 0, ¶ms->write); if (ret < 0) return gnutls_assert_val(ret); params->record_sw_size = 0; _gnutls_record_log("REC[%p]: Epoch #%u ready\n", session, params->epoch); params->initialized = 1; return 0; } #define CPY_COMMON dst->entity = src->entity; \ dst->kx_algorithm = src->kx_algorithm; \ memcpy( dst->cipher_suite, src->cipher_suite, 2); \ memcpy( dst->master_secret, src->master_secret, GNUTLS_MASTER_SIZE); \ memcpy( dst->client_random, src->client_random, GNUTLS_RANDOM_SIZE); \ memcpy( dst->server_random, src->server_random, GNUTLS_RANDOM_SIZE); \ memcpy( dst->session_id, src->session_id, TLS_MAX_SESSION_ID_SIZE); \ dst->session_id_size = src->session_id_size; \ dst->cert_type = src->cert_type; \ dst->compression_method = src->compression_method; \ dst->timestamp = src->timestamp; \ dst->max_record_recv_size = src->max_record_recv_size; \ dst->max_record_send_size = src->max_record_send_size static void _gnutls_set_resumed_parameters(gnutls_session_t session) { security_parameters_st *src = &session->internals.resumed_security_parameters; security_parameters_st *dst = &session->security_parameters; CPY_COMMON; dst->pversion = src->pversion; } /* Sets the current connection session to conform with the * Security parameters(pending session), and initializes encryption. * Actually it initializes and starts encryption ( so it needs * secrets and random numbers to have been negotiated) * This is to be called after sending the Change Cipher Spec packet. */ int _gnutls_connection_state_init(gnutls_session_t session) { int ret; /* Setup the master secret */ if ((ret = _gnutls_generate_master(session, 0)) < 0) return gnutls_assert_val(ret); return 0; } int _gnutls_epoch_get_compression(gnutls_session_t session, int epoch) { record_parameters_st *params; int ret; ret = _gnutls_epoch_get(session, epoch, ¶ms); if (ret < 0) return GNUTLS_COMP_UNKNOWN; return params->compression_algorithm; } /* Initializes the read connection session * (read encrypted data) */ int _gnutls_read_connection_state_init(gnutls_session_t session) { const uint16_t epoch_next = session->security_parameters.epoch_next; int ret; /* Update internals from CipherSuite selected. * If we are resuming just copy the connection session */ if (session->internals.resumed == RESUME_FALSE) { ret = _gnutls_set_kx(session, _gnutls_cipher_suite_get_kx_algo (session->security_parameters. cipher_suite)); if (ret < 0) return ret; } else if (session->security_parameters.entity == GNUTLS_CLIENT) _gnutls_set_resumed_parameters(session); ret = _gnutls_epoch_set_keys(session, epoch_next); if (ret < 0) return ret; _gnutls_handshake_log("HSK[%p]: Cipher Suite: %s\n", session, _gnutls_cipher_suite_get_name (session->security_parameters.cipher_suite)); session->security_parameters.epoch_read = epoch_next; return 0; } /* Initializes the write connection session * (write encrypted data) */ int _gnutls_write_connection_state_init(gnutls_session_t session) { const uint16_t epoch_next = session->security_parameters.epoch_next; int ret; /* Update internals from CipherSuite selected. * If we are resuming just copy the connection session */ if (session->internals.resumed == RESUME_FALSE) { ret = _gnutls_set_kx(session, _gnutls_cipher_suite_get_kx_algo (session->security_parameters. cipher_suite)); if (ret < 0) return ret; } else if (session->security_parameters.entity == GNUTLS_SERVER) _gnutls_set_resumed_parameters(session); ret = _gnutls_epoch_set_keys(session, epoch_next); if (ret < 0) return gnutls_assert_val(ret); _gnutls_handshake_log("HSK[%p]: Cipher Suite: %s\n", session, _gnutls_cipher_suite_get_name (session->security_parameters.cipher_suite)); _gnutls_handshake_log ("HSK[%p]: Initializing internal [write] cipher sessions\n", session); session->security_parameters.epoch_write = epoch_next; return 0; } /* Sets the specified kx algorithm into pending session */ static int _gnutls_set_kx(gnutls_session_t session, gnutls_kx_algorithm_t algo) { if (_gnutls_kx_is_ok(algo) == 0) { session->security_parameters.kx_algorithm = algo; } else return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); if (_gnutls_kx_priority(session, algo) < 0) return gnutls_assert_val(GNUTLS_E_UNWANTED_ALGORITHM); return 0; } static inline int epoch_resolve(gnutls_session_t session, unsigned int epoch_rel, uint16_t * epoch_out) { switch (epoch_rel) { case EPOCH_READ_CURRENT: *epoch_out = session->security_parameters.epoch_read; return 0; case EPOCH_WRITE_CURRENT: *epoch_out = session->security_parameters.epoch_write; return 0; case EPOCH_NEXT: *epoch_out = session->security_parameters.epoch_next; return 0; default: if (epoch_rel > 0xffffu) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); *epoch_out = epoch_rel; return 0; } } static inline record_parameters_st **epoch_get_slot(gnutls_session_t session, uint16_t epoch) { uint16_t epoch_index = epoch - session->security_parameters.epoch_min; if (epoch_index >= MAX_EPOCH_INDEX) { _gnutls_handshake_log ("Epoch %d out of range (idx: %d, max: %d)\n", (int) epoch, (int) epoch_index, MAX_EPOCH_INDEX); gnutls_assert(); return NULL; } /* The slot may still be empty (NULL) */ return &session->record_parameters[epoch_index]; } int _gnutls_epoch_get(gnutls_session_t session, unsigned int epoch_rel, record_parameters_st ** params_out) { uint16_t epoch; record_parameters_st **params; int ret; ret = epoch_resolve(session, epoch_rel, &epoch); if (ret < 0) return gnutls_assert_val(ret); params = epoch_get_slot(session, epoch); if (params == NULL || *params == NULL) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); *params_out = *params; return 0; } int _gnutls_epoch_alloc(gnutls_session_t session, uint16_t epoch, record_parameters_st ** out) { record_parameters_st **slot; _gnutls_record_log("REC[%p]: Allocating epoch #%u\n", session, epoch); slot = epoch_get_slot(session, epoch); /* If slot out of range or not empty. */ if (slot == NULL) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); if (*slot != NULL) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); *slot = gnutls_calloc(1, sizeof(record_parameters_st)); if (*slot == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); (*slot)->epoch = epoch; (*slot)->cipher = NULL; (*slot)->mac = NULL; (*slot)->compression_algorithm = GNUTLS_COMP_UNKNOWN; if (IS_DTLS(session)) _gnutls_write_uint16(epoch, UINT64DATA((*slot)->write. sequence_number)); if (out != NULL) *out = *slot; return 0; } static inline int epoch_is_active(gnutls_session_t session, record_parameters_st * params) { const security_parameters_st *sp = &session->security_parameters; if (params->epoch == sp->epoch_read) return 1; if (params->epoch == sp->epoch_write) return 1; if (params->epoch == sp->epoch_next) return 1; return 0; } static inline int epoch_alive(gnutls_session_t session, record_parameters_st * params) { if (params->usage_cnt > 0) return 1; return epoch_is_active(session, params); } void _gnutls_epoch_gc(gnutls_session_t session) { int i, j; unsigned int min_index = 0; _gnutls_record_log("REC[%p]: Start of epoch cleanup\n", session); /* Free all dead cipher state */ for (i = 0; i < MAX_EPOCH_INDEX; i++) { if (session->record_parameters[i] != NULL) { if (!epoch_is_active (session, session->record_parameters[i]) && session->record_parameters[i]->usage_cnt) _gnutls_record_log ("REC[%p]: Note inactive epoch %d has %d users\n", session, session->record_parameters[i]->epoch, session->record_parameters[i]-> usage_cnt); if (!epoch_alive (session, session->record_parameters[i])) { _gnutls_epoch_free(session, session-> record_parameters[i]); session->record_parameters[i] = NULL; } } } /* Look for contiguous NULLs at the start of the array */ for (i = 0; i < MAX_EPOCH_INDEX && session->record_parameters[i] == NULL; i++); min_index = i; /* Pick up the slack in the epoch window. */ for (i = 0, j = min_index; j < MAX_EPOCH_INDEX; i++, j++) session->record_parameters[i] = session->record_parameters[j]; /* Set the new epoch_min */ if (session->record_parameters[0] != NULL) session->security_parameters.epoch_min = session->record_parameters[0]->epoch; _gnutls_record_log("REC[%p]: End of epoch cleanup\n", session); } static inline void free_record_state(record_state_st * state, int d) { _gnutls_free_datum(&state->mac_secret); _gnutls_free_datum(&state->IV); _gnutls_free_datum(&state->key); _gnutls_auth_cipher_deinit(&state->cipher_state); if (state->compression_state.handle != NULL) _gnutls_comp_deinit(&state->compression_state, d); } void _gnutls_epoch_free(gnutls_session_t session, record_parameters_st * params) { _gnutls_record_log("REC[%p]: Epoch #%u freed\n", session, params->epoch); free_record_state(¶ms->read, 1); free_record_state(¶ms->write, 0); gnutls_free(params); }