/* * TLS server tickets callbacks implementation * * Copyright The Mbed TLS Contributors * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "common.h" #if defined(MBEDTLS_SSL_TICKET_C) #if defined(MBEDTLS_PLATFORM_C) #include "mbedtls/platform.h" #else #include #define mbedtls_calloc calloc #define mbedtls_free free #endif #include "mbedtls/ssl_internal.h" #include "mbedtls/ssl_ticket.h" #include "mbedtls/error.h" #include "mbedtls/platform_util.h" #include /* * Initialze context */ void mbedtls_ssl_ticket_init(mbedtls_ssl_ticket_context *ctx) { memset(ctx, 0, sizeof(mbedtls_ssl_ticket_context)); #if defined(MBEDTLS_THREADING_C) mbedtls_mutex_init(&ctx->mutex); #endif } #define MAX_KEY_BYTES 32 /* 256 bits */ #define TICKET_KEY_NAME_BYTES 4 #define TICKET_IV_BYTES 12 #define TICKET_CRYPT_LEN_BYTES 2 #define TICKET_AUTH_TAG_BYTES 16 #define TICKET_MIN_LEN ( TICKET_KEY_NAME_BYTES + \ TICKET_IV_BYTES + \ TICKET_CRYPT_LEN_BYTES + \ TICKET_AUTH_TAG_BYTES ) #define TICKET_ADD_DATA_LEN ( TICKET_KEY_NAME_BYTES + \ TICKET_IV_BYTES + \ TICKET_CRYPT_LEN_BYTES ) /* * Generate/update a key */ static int ssl_ticket_gen_key(mbedtls_ssl_ticket_context *ctx, unsigned char index) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; unsigned char buf[MAX_KEY_BYTES]; mbedtls_ssl_ticket_key *key = ctx->keys + index; #if defined(MBEDTLS_HAVE_TIME) key->generation_time = (uint32_t) mbedtls_time(NULL); #endif if ((ret = ctx->f_rng(ctx->p_rng, key->name, sizeof(key->name))) != 0) return (ret); if ((ret = ctx->f_rng(ctx->p_rng, buf, sizeof(buf))) != 0) return (ret); /* With GCM and CCM, same context can encrypt & decrypt */ ret = mbedtls_cipher_setkey(&key->ctx, buf, mbedtls_cipher_get_key_bitlen(&key->ctx), MBEDTLS_ENCRYPT); mbedtls_platform_zeroize(buf, sizeof(buf)); return (ret); } /* * Rotate/generate keys if necessary */ static int ssl_ticket_update_keys(mbedtls_ssl_ticket_context *ctx) { #if !defined(MBEDTLS_HAVE_TIME) ((void) ctx); #else if (ctx->ticket_lifetime != 0) { uint32_t current_time = (uint32_t) mbedtls_time(NULL); uint32_t key_time = ctx->keys[ctx->active].generation_time; if (current_time >= key_time && current_time - key_time < ctx->ticket_lifetime) { return (0); } ctx->active = 1 - ctx->active; return (ssl_ticket_gen_key(ctx, ctx->active)); } else #endif /* MBEDTLS_HAVE_TIME */ return (0); } /* * Setup context for actual use */ int mbedtls_ssl_ticket_setup(mbedtls_ssl_ticket_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, mbedtls_cipher_type_t cipher, uint32_t lifetime) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; const mbedtls_cipher_info_t *cipher_info; ctx->f_rng = f_rng; ctx->p_rng = p_rng; ctx->ticket_lifetime = lifetime; cipher_info = mbedtls_cipher_info_from_type(cipher); if (cipher_info == NULL) return (MBEDTLS_ERR_SSL_BAD_INPUT_DATA); if (cipher_info->mode != MBEDTLS_MODE_GCM && cipher_info->mode != MBEDTLS_MODE_CCM) { return (MBEDTLS_ERR_SSL_BAD_INPUT_DATA); } if (cipher_info->key_bitlen > 8 * MAX_KEY_BYTES) return (MBEDTLS_ERR_SSL_BAD_INPUT_DATA); #if defined(MBEDTLS_USE_PSA_CRYPTO) ret = mbedtls_cipher_setup_psa(&ctx->keys[0].ctx, cipher_info, TICKET_AUTH_TAG_BYTES); if (ret != 0 && ret != MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE) return (ret); /* We don't yet expect to support all ciphers through PSA, * so allow fallback to ordinary mbedtls_cipher_setup(). */ if (ret == MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE) #endif /* MBEDTLS_USE_PSA_CRYPTO */ if ((ret = mbedtls_cipher_setup(&ctx->keys[0].ctx, cipher_info)) != 0) return (ret); #if defined(MBEDTLS_USE_PSA_CRYPTO) ret = mbedtls_cipher_setup_psa(&ctx->keys[1].ctx, cipher_info, TICKET_AUTH_TAG_BYTES); if (ret != 0 && ret != MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE) return (ret); if (ret == MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE) #endif /* MBEDTLS_USE_PSA_CRYPTO */ if ((ret = mbedtls_cipher_setup(&ctx->keys[1].ctx, cipher_info)) != 0) return (ret); if ((ret = ssl_ticket_gen_key(ctx, 0)) != 0 || (ret = ssl_ticket_gen_key(ctx, 1)) != 0) { return (ret); } return (0); } /* * Create session ticket, with the following structure: * * struct { * opaque key_name[4]; * opaque iv[12]; * opaque encrypted_state<0..2^16-1>; * opaque tag[16]; * } ticket; * * The key_name, iv, and length of encrypted_state are the additional * authenticated data. */ int mbedtls_ssl_ticket_write(void *p_ticket, const mbedtls_ssl_session *session, unsigned char *start, const unsigned char *end, size_t *tlen, uint32_t *ticket_lifetime) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; mbedtls_ssl_ticket_context *ctx = p_ticket; mbedtls_ssl_ticket_key *key; unsigned char *key_name = start; unsigned char *iv = start + TICKET_KEY_NAME_BYTES; unsigned char *state_len_bytes = iv + TICKET_IV_BYTES; unsigned char *state = state_len_bytes + TICKET_CRYPT_LEN_BYTES; size_t clear_len, ciph_len; *tlen = 0; if (ctx == NULL || ctx->f_rng == NULL) return (MBEDTLS_ERR_SSL_BAD_INPUT_DATA); /* We need at least 4 bytes for key_name, 12 for IV, 2 for len 16 for tag, * in addition to session itself, that will be checked when writing it. */ MBEDTLS_SSL_CHK_BUF_PTR(start, end, TICKET_MIN_LEN); #if defined(MBEDTLS_THREADING_C) if ((ret = mbedtls_mutex_lock(&ctx->mutex)) != 0) return (ret); #endif if ((ret = ssl_ticket_update_keys(ctx)) != 0) goto cleanup; key = &ctx->keys[ctx->active]; *ticket_lifetime = ctx->ticket_lifetime; memcpy(key_name, key->name, TICKET_KEY_NAME_BYTES); if ((ret = ctx->f_rng(ctx->p_rng, iv, TICKET_IV_BYTES)) != 0) goto cleanup; /* Dump session state */ if ((ret = mbedtls_ssl_session_save(session, state, end - state, &clear_len)) != 0 || (unsigned long) clear_len > 65535) { goto cleanup; } state_len_bytes[0] = (clear_len >> 8) & 0xff; state_len_bytes[1] = (clear_len) & 0xff; /* Encrypt and authenticate */ if ((ret = mbedtls_cipher_auth_encrypt_ext(&key->ctx, iv, TICKET_IV_BYTES, /* Additional data: key name, IV and length */ key_name, TICKET_ADD_DATA_LEN, state, clear_len, state, end - state, &ciph_len, TICKET_AUTH_TAG_BYTES)) != 0) { goto cleanup; } if (ciph_len != clear_len + TICKET_AUTH_TAG_BYTES) { ret = MBEDTLS_ERR_SSL_INTERNAL_ERROR; goto cleanup; } *tlen = TICKET_MIN_LEN + ciph_len - TICKET_AUTH_TAG_BYTES; cleanup: #if defined(MBEDTLS_THREADING_C) if (mbedtls_mutex_unlock(&ctx->mutex) != 0) return (MBEDTLS_ERR_THREADING_MUTEX_ERROR); #endif return (ret); } /* * Select key based on name */ static mbedtls_ssl_ticket_key *ssl_ticket_select_key( mbedtls_ssl_ticket_context *ctx, const unsigned char name[4]) { unsigned char i; for (i = 0; i < sizeof(ctx->keys) / sizeof(*ctx->keys); i++) if (memcmp(name, ctx->keys[i].name, 4) == 0) return (&ctx->keys[i]); return (NULL); } /* * Load session ticket (see mbedtls_ssl_ticket_write for structure) */ int mbedtls_ssl_ticket_parse(void *p_ticket, mbedtls_ssl_session *session, unsigned char *buf, size_t len) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; mbedtls_ssl_ticket_context *ctx = p_ticket; mbedtls_ssl_ticket_key *key; unsigned char *key_name = buf; unsigned char *iv = buf + TICKET_KEY_NAME_BYTES; unsigned char *enc_len_p = iv + TICKET_IV_BYTES; unsigned char *ticket = enc_len_p + TICKET_CRYPT_LEN_BYTES; size_t enc_len, clear_len; if (ctx == NULL || ctx->f_rng == NULL) return (MBEDTLS_ERR_SSL_BAD_INPUT_DATA); if (len < TICKET_MIN_LEN) return (MBEDTLS_ERR_SSL_BAD_INPUT_DATA); #if defined(MBEDTLS_THREADING_C) if ((ret = mbedtls_mutex_lock(&ctx->mutex)) != 0) return (ret); #endif if ((ret = ssl_ticket_update_keys(ctx)) != 0) goto cleanup; enc_len = (enc_len_p[0] << 8) | enc_len_p[1]; if (len != TICKET_MIN_LEN + enc_len) { ret = MBEDTLS_ERR_SSL_BAD_INPUT_DATA; goto cleanup; } /* Select key */ if ((key = ssl_ticket_select_key(ctx, key_name)) == NULL) { /* We can't know for sure but this is a likely option unless we're * under attack - this is only informative anyway */ ret = MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED; goto cleanup; } /* Decrypt and authenticate */ if ((ret = mbedtls_cipher_auth_decrypt_ext(&key->ctx, iv, TICKET_IV_BYTES, /* Additional data: key name, IV and length */ key_name, TICKET_ADD_DATA_LEN, ticket, enc_len + TICKET_AUTH_TAG_BYTES, ticket, enc_len, &clear_len, TICKET_AUTH_TAG_BYTES)) != 0) { if (ret == MBEDTLS_ERR_CIPHER_AUTH_FAILED) ret = MBEDTLS_ERR_SSL_INVALID_MAC; goto cleanup; } if (clear_len != enc_len) { ret = MBEDTLS_ERR_SSL_INTERNAL_ERROR; goto cleanup; } /* Actually load session */ if ((ret = mbedtls_ssl_session_load(session, ticket, clear_len)) != 0) goto cleanup; #if defined(MBEDTLS_HAVE_TIME) { /* Check for expiration */ mbedtls_time_t current_time = mbedtls_time(NULL); if (current_time < session->start || (uint32_t)(current_time - session->start) > ctx->ticket_lifetime) { ret = MBEDTLS_ERR_SSL_SESSION_TICKET_EXPIRED; goto cleanup; } } #endif cleanup: #if defined(MBEDTLS_THREADING_C) if (mbedtls_mutex_unlock(&ctx->mutex) != 0) return (MBEDTLS_ERR_THREADING_MUTEX_ERROR); #endif return (ret); } /* * Free context */ void mbedtls_ssl_ticket_free(mbedtls_ssl_ticket_context *ctx) { mbedtls_cipher_free(&ctx->keys[0].ctx); mbedtls_cipher_free(&ctx->keys[1].ctx); #if defined(MBEDTLS_THREADING_C) mbedtls_mutex_free(&ctx->mutex); #endif mbedtls_platform_zeroize(ctx, sizeof(mbedtls_ssl_ticket_context)); } #endif /* MBEDTLS_SSL_TICKET_C */