From 794cb5c7a9ec9495d9751a0397b4cf681909e5a5 Mon Sep 17 00:00:00 2001 From: Sean Parkinson Date: Wed, 3 Feb 2021 13:30:38 +1000 Subject: [PATCH] TLS Session Ticket: default encryption callback Encrypts with ChaCha20-Poly1305 or AES-GCM. Two keys in rotation. Key used for encryption until ticket lifetime goes beyond expirary (default 1 hour). If key can still be used for decryption, encrypt with other key. Private random used to generate keys. --- examples/echoserver/echoserver.c | 9 +- examples/server/server.c | 9 +- src/internal.c | 483 ++++++++++++++++++++++++++++++- src/ssl.c | 68 +++++ tests/api.c | 57 +++- wolfssl/internal.h | 54 ++++ wolfssl/openssl/ssl.h | 3 + wolfssl/ssl.h | 24 ++ wolfssl/test.h | 2 +- 9 files changed, 687 insertions(+), 22 deletions(-) diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 6e94f50c3..1ef3007f8 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -165,9 +165,8 @@ THREAD_RETURN CYASSL_THREAD echoserver_test(void* args) CyaSSL_CTX_set_default_passwd_cb(ctx, PasswordCallBack); #endif -#if defined(HAVE_SESSION_TICKET) && \ - ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || \ - defined(HAVE_AESGCM)) +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || defined(HAVE_AESGCM)) if (TicketInit() != 0) err_sys("unable to setup Session Ticket Key context"); wolfSSL_CTX_set_TicketEncCb(ctx, myTicketEncCb); @@ -521,8 +520,8 @@ THREAD_RETURN CYASSL_THREAD echoserver_test(void* args) fdCloseSession(Task_self()); #endif -#if defined(HAVE_SESSION_TICKET) && defined(HAVE_CHACHA) && \ - defined(HAVE_POLY1305) +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || defined(HAVE_AESGCM)) TicketCleanup(); #endif diff --git a/examples/server/server.c b/examples/server/server.c index 2c4f81609..4102f45a6 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -1800,9 +1800,8 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) wolfSSL_CTX_SetIOSend(ctx, SimulateWantWriteIOSendCb); } -#if defined(HAVE_SESSION_TICKET) && \ - ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || \ - defined(HAVE_AESGCM)) +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || defined(HAVE_AESGCM)) if (TicketInit() != 0) err_sys_ex(catastrophic, "unable to setup Session Ticket Key context"); wolfSSL_CTX_set_TicketEncCb(ctx, myTicketEncCb); @@ -2835,8 +2834,8 @@ exit: fdCloseSession(Task_self()); #endif -#if defined(HAVE_SESSION_TICKET) && defined(HAVE_CHACHA) && \ - defined(HAVE_POLY1305) +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || defined(HAVE_AESGCM)) TicketCleanup(); #endif diff --git a/src/internal.c b/src/internal.c index 67c27f1f7..81aab0621 100644 --- a/src/internal.c +++ b/src/internal.c @@ -42,6 +42,24 @@ * read timeout. By default we resend in two more cases, when we receive: * - an out of order last msg of the peer's flight * - a duplicate of the first msg from the peer's flight + * WOLFSSL_NO_DEF_TICKET_ENC_CB: + * No default ticket encryption callback. + * Server only. + * Application must set its own callback to use session tickets. + * WOLFSSL_TICKET_ENC_CHACHA20_POLY1305 + * Use ChaCha20-Poly1305 to encrypt/decrypt session tickets in default + * callback. Default algorithm if none defined and algorithms compiled in. + * Server only. + * WOLFSSL_TICKET_ENC_AES128_GCM + * Use AES128-GCM to encrypt/decrypt session tickets in default callback. + * Server only. Default algorithm if ChaCha20/Poly1305 not compiled in. + * WOLFSSL_TICKET_ENC_AES256_GCM + * Use AES256-GCM to encrypt/decrypt session tickets in default callback. + * Server only. + * WOLFSSL_TICKET_DECRYPT_NO_CREATE + * Default callback will not request creation of new ticket on successful + * decryption. + * Server only. */ @@ -129,10 +147,25 @@ WOLFSSL_CALLBACKS needs LARGE_STATIC_BUFFERS, please add LARGE_STATIC_BUFFERS #ifdef WOLFSSL_DTLS static int SendHelloVerifyRequest(WOLFSSL*, const byte*, byte); #endif /* WOLFSSL_DTLS */ -#endif + +#endif /* !NO_WOLFSSL_SERVER */ #endif /* !WOLFSSL_NO_TLS12 */ +#ifndef NO_WOLFSSL_SERVER + #if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + static int TicketEncCbCtx_Init(WOLFSSL_CTX* ctx, + TicketEncCbCtx* keyCtx); + static void TicketEncCbCtx_Free(TicketEncCbCtx* keyCtx); + static int DefTicketEncCb(WOLFSSL* ssl, + byte key_name[WOLFSSL_TICKET_NAME_SZ], + byte iv[WOLFSSL_TICKET_IV_SZ], + byte mac[WOLFSSL_TICKET_MAC_SZ], + int enc, byte* ticket, int inLen, int* outLen, + void* userCtx); + #endif +#endif + #ifdef WOLFSSL_DTLS static WC_INLINE int DtlsCheckWindow(WOLFSSL* ssl); static WC_INLINE int DtlsUpdateWindow(WOLFSSL* ssl); @@ -1781,6 +1814,12 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) #endif /* HAVE_EXTENDED_MASTER && !NO_WOLFSSL_CLIENT */ #if defined(HAVE_SESSION_TICKET) && !defined(NO_WOLFSSL_SERVER) +#ifndef WOLFSSL_NO_DEF_TICKET_ENC_CB + ret = TicketEncCbCtx_Init(ctx, &ctx->ticketKeyCtx); + if (ret != 0) return ret; + ctx->ticketEncCb = DefTicketEncCb; + ctx->ticketEncCtx = (void*)&ctx->ticketKeyCtx; +#endif ctx->ticketHint = SESSION_TICKET_HINT_DEFAULT; #endif @@ -1913,7 +1952,7 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) #ifdef HAVE_ECC if (ctx->staticKE.ecKey) FreeDer(&ctx->staticKE.ecKey); - #endif + #endif #endif #ifdef WOLFSSL_STATIC_MEMORY if (ctx->heap != NULL) { @@ -1949,6 +1988,10 @@ void FreeSSL_Ctx(WOLFSSL_CTX* ctx) void* heap = ctx->heap; WOLFSSL_MSG("CTX ref count down to 0, doing full free"); SSL_CtxResourceFree(ctx); +#if defined(HAVE_SESSION_TICKET) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) + TicketEncCbCtx_Free(&ctx->ticketKeyCtx); +#endif wc_FreeMutex(&ctx->countMutex); #ifdef WOLFSSL_STATIC_MEMORY if (ctx->onHeap == 0) { @@ -12970,7 +13013,6 @@ static int DoHandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, else { ShrinkInputBuffer(ssl, NO_FORCED_FREE); } - } #if defined(WOLFSSL_ASYNC_CRYPT) || defined(WOLFSSL_NONBLOCK_OCSP) @@ -17138,7 +17180,7 @@ int CreateOcspResponse(WOLFSSL* ssl, OcspRequest** ocspRequest, #endif /* !NO_WOLFSSL_SERVER */ #if (!defined(WOLFSSL_NO_TLS12) && !defined(NO_CERTS)) \ - || defined(HAVE_SESSION_TICKET) + || (defined(HAVE_SESSION_TICKET) && !defined(NO_WOLFSSL_SERVER)) static int cipherExtraData(WOLFSSL* ssl) { /* Cipher data that may be added by BuildMessage */ @@ -29146,6 +29188,435 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, return ret; } +#ifndef WOLFSSL_NO_DEF_TICKET_ENC_CB + +/* Initialize the context for session ticket encryption. + * + * @param [in] ctx SSL context. + * @param [in] keyCtx Context for session ticket encryption. + * @return 0 on success. + * @return BAD_MUTEX_E when initializing mutex fails. + */ +static int TicketEncCbCtx_Init(WOLFSSL_CTX* ctx, TicketEncCbCtx* keyCtx) +{ + int ret = 0; + + XMEMSET(keyCtx, 0, sizeof(*keyCtx)); + keyCtx->ctx = ctx; + +#ifndef SINGLE_THREADED + ret = wc_InitMutex(&keyCtx->mutex); +#endif + + return ret; +} + +/* Setup the session ticket encryption context for this. + * + * Initialize RNG, generate name, generate primeary key and set primary key + * expirary. + * + * @param [in] keyCtx Context for session ticket encryption. + * @param [in] heap Dynamic memory allocation hint. + * @param [in] devId Device identifier. + * @return 0 on success. + * @return Other value when random number generator fails. + */ +static int TicketEncCbCtx_Setup(TicketEncCbCtx* keyCtx, void* heap, int devId) +{ + int ret; + +#ifndef SINGLE_THREADED + ret = 0; + + /* Check that key wasn't set up while waiting. */ + if (keyCtx->expirary[0] == 0) +#endif + { + ret = wc_InitRng_ex(&keyCtx->rng, heap, devId); + if (ret == 0) { + ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->name, + sizeof(keyCtx->name)); + } + if (ret == 0) { + /* Mask of the bottom bit - used for index of key. */ + keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1] &= 0xfe; + + /* Generate initial primary key. */ + ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->key[0], + WOLFSSL_TICKET_KEY_SZ); + } + if (ret == 0) { + keyCtx->expirary[0] = LowResTimer() + WOLFSSL_TICKET_KEY_LIFETIME; + } + } + + return ret; +} +/* Free the context for session ticket encryption. + * + * Zeroize keys and name. + * + * @param [in] keyCtx Context for session ticket encryption. + */ +static void TicketEncCbCtx_Free(TicketEncCbCtx* keyCtx) +{ + /* Zeroize sensitive data. */ + ForceZero(keyCtx->name, sizeof(keyCtx->name)); + ForceZero(keyCtx->key[0], sizeof(keyCtx->key[0])); + ForceZero(keyCtx->key[1], sizeof(keyCtx->key[1])); + +#ifndef SINGLE_THREADED + wc_FreeMutex(&keyCtx->mutex); +#endif + wc_FreeRng(&keyCtx->rng); +} + +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) && \ + !defined(WOLFSSL_TICKET_ENC_AES128_GCM) && \ + !defined(WOLFSSL_TICKET_ENC_AES256_GCM) +/* Ticket encryption/decryption implementation. + * + * @param [in] key Key for encryption/decryption. + * @param [in] keyLen Length of key in bytes. + * @param [in] iv IV/Nonce for encryption/decryption. + * @param [in] aad Additional authentication data. + * @param [in] aadSz Length of additional authentication data. + * @param [in] in Data to encrypt/decrypt. + * @param [in] inLen Length of encrypted data. + * @param [out] out Resulting data from encrypt/decrypt. + * @param [out] outLen Size of resulting data. + * @param [in] tag Authentication tag for encrypted data. + * @param [in] heap Dynamic memory allocation data hint. + * @param [in] enc 1 when encrypting, 0 when decrypting. + * @return 0 on success. + * @return Other value when encryption/decryption fails. + */ +static int TicketEncDec(byte* key, int keyLen, byte* iv, byte* aad, int aadSz, + byte* in, int inLen, byte* out, int* outLen, byte* tag, + void* heap, int enc) +{ + int ret; + + (void)keyLen; + (void)heap; + + if (enc) { + ret = wc_ChaCha20Poly1305_Encrypt(key, iv, aad, aadSz, in, inLen, out, + tag); + } + else { + ret = wc_ChaCha20Poly1305_Decrypt(key, iv, aad, aadSz, in, inLen, tag, + out); + } + + *outLen = inLen; + + return ret; +} +#elif defined(HAVE_AESGCM) +/* Ticket encryption/decryption implementation. + * + * @param [in] key Key for encryption/decryption. + * @param [in] keyLen Length of key in bytes. + * @param [in] iv IV/Nonce for encryption/decryption. + * @param [in] aad Additional authentication data. + * @param [in] aadSz Length of additional authentication data. + * @param [in] in Data to encrypt/decrypt. + * @param [in] inLen Length of encrypted data. + * @param [out] out Resulting data from encrypt/decrypt. + * @param [out] outLen Size of resulting data. + * @param [in] tag Authentication tag for encrypted data. + * @param [in] heap Dynamic memory allocation data hint. + * @param [in] enc 1 when encrypting, 0 when decrypting. + * @return 0 on success. + * @return MEMORY_E when dynamic memory allocation fails. + * @return Other value when encryption/decryption fails. + */ +static int TicketEncDec(byte* key, int keyLen, byte* iv, byte* aad, int aadSz, + byte* in, int inLen, byte* out, int* outLen, byte* tag, + void* heap, int enc) +{ + int ret; +#ifdef WOLFSSL_SMALL_STACK + Aes* aes; +#else + Aes aes[1]; +#endif + + (void)heap; + +#ifdef WOLFSSL_SMALL_STACK + aes = (Aes*)XMALLOC(sizeof(Aes), heap, DYNAMIC_TYPE_TMP_BUFFER); + if (aes == NULL) + return MEMORY_E; +#endif + + if (enc) { + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesGcmSetKey(aes, key, keyLen); + } + if (ret == 0) { + ret = wc_AesGcmEncrypt(aes, in, out, inLen, iv, GCM_NONCE_MID_SZ, + tag, AES_BLOCK_SIZE, aad, aadSz); + } + wc_AesFree(aes); + } + else { + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesGcmSetKey(aes, key, keyLen); + } + if (ret == 0) { + ret = wc_AesGcmDecrypt(aes, in, out, inLen, iv, GCM_NONCE_MID_SZ, + tag, AES_BLOCK_SIZE, aad, aadSz); + } + wc_AesFree(aes); + } + +#ifdef WOLFSSL_SMALL_STACK + XFREE(aes, heap, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + *outLen = inLen; + + return ret; +} +#else + #error "No encryption algorithm available for default ticket encryption." +#endif + +/* Choose a key to use for encryption. + * + * Generate a new key if the current ones are expired. + * If the secondary key has not been used and the primary key has expired then + * generate a new primary key. + * + * @param [in] Ticket encryption callback context. + * @param [in] Session ticket lifetime. + * @param [out] Index of key to use for encryption. + * @return 0 on success. + * @return Other value when random number generation fails. + */ +static int TicketEncCbCtx_ChooseKey(TicketEncCbCtx* keyCtx, int ticketHint, + int* keyIdx) +{ + int ret = 0; + + /* Get new current time as lock may have taken some time. */ + word32 now = LowResTimer(); + + /* Check expirary of primary key for encrypt. */ + if (keyCtx->expirary[0] >= now + ticketHint) { + *keyIdx = 0; + } + /* Check expirary of primary key for encrypt. */ + else if (keyCtx->expirary[1] >= now + ticketHint) { + *keyIdx = 1; + } + /* No key available to use. */ + else { + int genKey; + + /* Generate which ever key is expired for decrypt - primary first. */ + if (keyCtx->expirary[0] < now) { + genKey = 0; + } + else if (keyCtx->expirary[1] < now) { + genKey = 1; + } + /* Timeouts and expirary should not allow this to happen. */ + else { + return BAD_STATE_E; + } + + /* Generate the required key */ + ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->key[genKey], + WOLFSSL_TICKET_KEY_SZ); + if (ret == 0) { + keyCtx->expirary[genKey] = now + WOLFSSL_TICKET_KEY_LIFETIME; + *keyIdx = genKey; + } + } + + return ret; +} + +/* Default Session Ticket encryption/decryption callback. + * + * Use ChaCha20-Poly1305 or AES-GCM to encrypt/decrypt the ticket. + * Two keys are used: + * - When the first expires for encryption, then use the other. + * - Don't encrypt with key if the ticket lifetime will go beyond expirary. + * - Generate a new primary key when primary key expired for decrypt and + * no secondary key is activate for encryption. + * - Generate a new secondary key when expired and needed. + * - Calculate expirary starting from first encrypted ticket. + * - Key name has last bit set to indicate index of key. + * Keys expire for decryption after ticket key lifetime from the first encrypted + * ticket. + * Keys can only be use for encryption while the ticket hint does not exceed + * the key lifetime. + * Lifetime of a key must be greater than the lifetime of a ticket. This means + * that if one ticket is only valid for decryption, then the other will be + * valid for encryption. + * AAD = key_name | iv | ticket len (16-bits network order) + * + * @param [in] ssl SSL connection. + * @param [in,out] key_name Name of key from client. + * Encrypt: name of key returned. + * Decrypt: name from ticket message to check. + * @param [in] iv IV to use in encryption/decryption. + * @param [in] mac MAC for authentication of encrypted data. + * @param [in] enc 1 when encrypting ticket, 0 when decrypting. + * @param [in,out] ticket Encrypted/decrypted session ticket bytes. + * @param [in] inLen Length of incoming ticket. + * @param [out] outLen Length of outgoing ticket. + * @param [in] userCtx Context for encryption/decryption of ticket. + * @return WOLFSSL_TICKET_RET_OK when successful. + * @return WOLFSSL_TICKET_RET_CREATE when successful and a new ticket is to + * be created for TLS 1.2 and below. + * @return WOLFSSL_TICKET_RET_REJECT when failed to produce valid encrypted or + * decrypted ticket. + * @return WOLFSSL_TICKET_RET_FATAL when key name does not match. + */ +static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], + byte iv[WOLFSSL_TICKET_IV_SZ], + byte mac[WOLFSSL_TICKET_MAC_SZ], + int enc, byte* ticket, int inLen, int* outLen, + void* userCtx) +{ + int ret; + TicketEncCbCtx* keyCtx = (TicketEncCbCtx*)userCtx; + WOLFSSL_CTX* ctx = keyCtx->ctx; + word16 sLen = XHTONS(inLen); + byte aad[WOLFSSL_TICKET_NAME_SZ + WOLFSSL_TICKET_IV_SZ + sizeof(sLen)]; + int aadSz = WOLFSSL_TICKET_NAME_SZ + WOLFSSL_TICKET_IV_SZ + sizeof(sLen); + byte* p = aad; + int keyIdx = 0; + + /* Check we have setup the RNG, name and primary key. */ + if (keyCtx->expirary[0] == 0) { +#ifndef SINGLE_THREADED + /* Lock around access to expirary and key - stop initial key being + * generated twice at the same time. */ + if (wc_LockMutex(&keyCtx->mutex) != 0) { + WOLFSSL_MSG("Couldn't lock key context mutex"); + return WOLFSSL_TICKET_RET_REJECT; + } +#endif + /* Sets expirary of primary key in setup. */ + ret = TicketEncCbCtx_Setup(keyCtx, ssl->ctx->heap, ssl->ctx->devId); +#ifndef SINGLE_THREADED + wc_UnLockMutex(&keyCtx->mutex); +#endif + if (ret != 0) + return ret; + } + + if (enc) { + /* Return the name of the key - missing key index. */ + XMEMCPY(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ); + + /* Generate a new IV into buffer to be returned. + * Don't use the RNG in keyCtx as it's for generating private data. */ + ret = wc_RNG_GenerateBlock(ssl->rng, iv, WOLFSSL_TICKET_IV_SZ); + if (ret != 0) { + return WOLFSSL_TICKET_RET_REJECT; + } + } + else { + /* Mask of last bit that is the key index. */ + byte lastByte = key_name[WOLFSSL_TICKET_NAME_SZ - 1] & 0xfe; + + /* For decryption, see if we know this key - check all but last byte. */ + if (XMEMCMP(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ - 1) != 0) { + return WOLFSSL_TICKET_RET_FATAL; + } + /* Ensure last byte without index bit matches too. */ + if (lastByte != keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1]) { + return WOLFSSL_TICKET_RET_FATAL; + } + } + + /* Build AAD from: key name, iv, and length of ticket. */ + XMEMCPY(p, keyCtx->name, WOLFSSL_TICKET_NAME_SZ); + p += WOLFSSL_TICKET_NAME_SZ; + XMEMCPY(p, iv, WOLFSSL_TICKET_IV_SZ); + p += WOLFSSL_TICKET_IV_SZ; + XMEMCPY(p, &sLen, sizeof(sLen)); + + /* Encrypt ticket. */ + if (enc) { + word32 now; + + now = LowResTimer(); + /* As long as encryption expirary isn't imminent - no lock. */ + if (keyCtx->expirary[0] > now + ctx->ticketHint) { + keyIdx = 0; + } + else if (keyCtx->expirary[1] > now + ctx->ticketHint) { + keyIdx = 1; + } + else { +#ifndef SINGLE_THREADED + /* Lock around access to expirary and key - stop key being generated + * twice at the same time. */ + if (wc_LockMutex(&keyCtx->mutex) != 0) { + WOLFSSL_MSG("Couldn't lock key context mutex"); + return WOLFSSL_TICKET_RET_REJECT; + } +#endif + ret = TicketEncCbCtx_ChooseKey(keyCtx, ctx->ticketHint, &keyIdx); +#ifndef SINGLE_THREADED + wc_UnLockMutex(&keyCtx->mutex); +#endif + if (ret != 0) { + return WOLFSSL_TICKET_RET_REJECT; + } + } + /* Set the name of the key to the index chosen. */ + key_name[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; + /* Update AAD too. */ + aad[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; + + /* Encrypt ticket data. */ + ret = TicketEncDec(keyCtx->key[keyIdx], WOLFSSL_TICKET_KEY_SZ, iv, aad, + aadSz, ticket, inLen, ticket, outLen, mac, ssl->heap, + 1); + if (ret != 0) return WOLFSSL_TICKET_RET_REJECT; + } + /* Decrypt ticket. */ + else { + /* Get index of key from name. */ + keyIdx = key_name[WOLFSSL_TICKET_NAME_SZ - 1] & 0x1; + /* Update AAD with index. */ + aad[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; + + /* Check expirary */ + if (keyCtx->expirary[keyIdx] <= LowResTimer()) { + return WOLFSSL_TICKET_RET_REJECT; + } + + /* Decrypt ticket data. */ + ret = TicketEncDec(keyCtx->key[keyIdx], WOLFSSL_TICKET_KEY_SZ, iv, aad, + aadSz, ticket, inLen, ticket, outLen, mac, ssl->heap, + 0); + if (ret != 0) { + return WOLFSSL_TICKET_RET_REJECT; + } + } + +#ifndef WOLFSSL_TICKET_DECRYPT_NO_CREATE + if (!IsAtLeastTLSv1_3(ssl->version) && !enc) + return WOLFSSL_TICKET_RET_CREATE; +#endif + return WOLFSSL_TICKET_RET_OK; +} + +#endif /* !WOLFSSL_NO_DEF_TICKET_ENC_CB */ + #endif /* HAVE_SESSION_TICKET */ #ifndef WOLFSSL_NO_TLS12 @@ -29839,9 +30310,9 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, ssl->arrays->preMasterSz = private_key->dp->size; ssl->peerEccKeyPresent = 1; - + #if defined(WOLFSSL_TLS13) || defined(HAVE_FFDHE) - /* client_hello may have sent FFEDH2048, which sets namedGroup, + /* client_hello may have sent FFEDH2048, which sets namedGroup, but that is not being used, so clear it */ /* resolves issue with server side wolfSSL_get_curve_name */ ssl->namedGroup = 0; diff --git a/src/ssl.c b/src/ssl.c index 7077b8c88..4c5bf6193 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -47412,11 +47412,79 @@ int wolfSSL_CTX_set_tlsext_ticket_key_cb(WOLFSSL_CTX *ctx, int (*cb)( return WOLFSSL_SUCCESS; } + #endif /* HAVE_SESSION_TICKET */ #endif /* OPENSSL_ALL || WOLFSSL_NGINX || WOLFSSL_HAPROXY || OPENSSL_EXTRA || HAVE_LIGHTY */ +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_WOLFSSL_SERVER) +/* Serialize the session ticket encryption keys. + * + * @param [in] ctx SSL/TLS context object. + * @param [in] keys Buffer to hold session ticket keys. + * @param [in] keylen Length of buffer. + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL or keylen is not the + * correct length. + */ +long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, + unsigned char *keys, int keylen) +{ + if (ctx == NULL || keys == NULL) { + return WOLFSSL_FAILURE; + } + if (keylen != WOLFSSL_TICKET_KEYS_SZ) { + return WOLFSSL_FAILURE; + } + + XMEMCPY(keys, ctx->ticketKeyCtx.name, WOLFSSL_TICKET_NAME_SZ); + keys += WOLFSSL_TICKET_NAME_SZ; + XMEMCPY(keys, ctx->ticketKeyCtx.key[0], WOLFSSL_TICKET_KEY_SZ); + keys += WOLFSSL_TICKET_KEY_SZ; + XMEMCPY(keys, ctx->ticketKeyCtx.key[1], WOLFSSL_TICKET_KEY_SZ); + keys += WOLFSSL_TICKET_KEY_SZ; + c32toa(ctx->ticketKeyCtx.expirary[0], keys); + keys += OPAQUE32_LEN; + c32toa(ctx->ticketKeyCtx.expirary[1], keys); + + return WOLFSSL_SUCCESS; +} + +/* Deserialize the session ticket encryption keys. + * + * @param [in] ctx SSL/TLS context object. + * @param [in] keys Session ticket keys. + * @param [in] keylen Length of data. + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL or keylen is not the + * correct length. + */ +long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, + unsigned char *keys, int keylen) +{ + if (ctx == NULL || keys == NULL) { + return WOLFSSL_FAILURE; + } + if (keylen != WOLFSSL_TICKET_KEYS_SZ) { + return WOLFSSL_FAILURE; + } + + XMEMCPY(ctx->ticketKeyCtx.name, keys, WOLFSSL_TICKET_NAME_SZ); + keys += WOLFSSL_TICKET_NAME_SZ; + XMEMCPY(ctx->ticketKeyCtx.key[0], keys, WOLFSSL_TICKET_KEY_SZ); + keys += WOLFSSL_TICKET_KEY_SZ; + XMEMCPY(ctx->ticketKeyCtx.key[1], keys, WOLFSSL_TICKET_KEY_SZ); + keys += WOLFSSL_TICKET_KEY_SZ; + ato32(keys, &ctx->ticketKeyCtx.expirary[0]); + keys += OPAQUE32_LEN; + ato32(keys, &ctx->ticketKeyCtx.expirary[1]); + + return WOLFSSL_SUCCESS; +} +#endif + #if defined(OPENSSL_ALL) || defined(WOLFSSL_NGINX) || defined(WOLFSSL_HAPROXY) #ifdef HAVE_OCSP /* Not an OpenSSL API. */ diff --git a/tests/api.c b/tests/api.c index 004701a81..f44e8def7 100644 --- a/tests/api.c +++ b/tests/api.c @@ -2646,9 +2646,8 @@ static THREAD_RETURN WOLFSSL_THREAD test_server_nofail(void* args) ctx = wolfSSL_CTX_new(method); } -#if defined(HAVE_SESSION_TICKET) && \ - ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || \ - defined(HAVE_AESGCM)) +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || defined(HAVE_AESGCM)) TicketInit(); wolfSSL_CTX_set_TicketEncCb(ctx, myTicketEncCb); #endif @@ -2834,8 +2833,8 @@ done: wc_ecc_fp_free(); /* free per thread cache */ #endif -#if defined(HAVE_SESSION_TICKET) && defined(HAVE_CHACHA) && \ - defined(HAVE_POLY1305) +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || defined(HAVE_AESGCM)) TicketCleanup(); #endif @@ -31665,6 +31664,53 @@ static void test_wolfSSL_SESSION(void) #endif } +static void test_wolfSSL_ticket_keys(void) +{ +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx; + byte keys[WOLFSSL_TICKET_KEYS_SZ]; + + AssertNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + + AssertIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(NULL, NULL, 0), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(ctx, NULL, 0), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(ctx, keys, 0), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(NULL, keys, 0), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(NULL, NULL, sizeof(keys)), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(ctx, NULL, sizeof(keys)), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(NULL, keys, sizeof(keys)), + WOLFSSL_FAILURE); + + AssertIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(NULL, NULL, 0), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(ctx, NULL, 0), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(ctx, keys, 0), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(NULL, keys, 0), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(NULL, NULL, sizeof(keys)), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(ctx, NULL, sizeof(keys)), + WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(NULL, keys, sizeof(keys)), + WOLFSSL_FAILURE); + + AssertIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(ctx, keys, sizeof(keys)), + WOLFSSL_SUCCESS); + AssertIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(ctx, keys, sizeof(keys)), + WOLFSSL_SUCCESS); + + wolfSSL_CTX_free(ctx); +#endif +} #ifndef NO_BIO @@ -40255,6 +40301,7 @@ void ApiTest(void) test_wolfSSL_BIO_f_md(); #endif test_wolfSSL_SESSION(); + test_wolfSSL_ticket_keys(); test_wolfSSL_DES_ecb_encrypt(); test_wolfSSL_sk_GENERAL_NAME(); test_wolfSSL_MD4(); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index f953a143c..75360ebf9 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -114,6 +114,15 @@ #ifdef HAVE_CURVE448 #include #endif +#ifndef WOLFSSL_NO_DEF_TICKET_ENC_CB + #if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) && \ + !defined(WOLFSSL_TICKET_ENC_AES128_GCM) && \ + !defined(WOLFSSL_TICKET_ENC_AES256_GCM) + #include + #else + #include + #endif +#endif #include #include @@ -1585,6 +1594,26 @@ enum Misc { #define SESSION_TICKET_HINT_DEFAULT 300 #endif +#if !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(WOLFSSL_NO_SERVER) + /* Check chosen encryption is available. */ + #if !(defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) && \ + defined(WOLFSSL_TICKET_ENC_CHACHA20_POLY1305) + #error "ChaCha20-Poly1305 not availble for default ticket encryption" + #endif + #if !defined(HAVE_AESGCM) && (defined(WOLFSSL_TICKET_ENC_AES128_GCM) || \ + defined(WOLFSSL_TICKET_ENC_AES256_GCM)) + #error "AES-GCM not availble for default ticket encryption" + #endif + + #ifndef WOLFSSL_TICKET_KEY_LIFETIME + /* Default lifetime is 1 hour from issue of first ticket with key. */ + #define WOLFSSL_TICKET_KEY_LIFETIME (60 * 60) + #endif + #if WOLFSSL_TICKET_KEY_LIFETIME <= SESSION_TICKET_HINT_DEFAULT + #error "Ticket Key lifetime must be longer than ticket life hint." + #endif +#endif + /* don't use extra 3/4k stack space unless need to */ #ifdef HAVE_NTRU @@ -2473,6 +2502,28 @@ typedef struct SessionTicket { word16 size; } SessionTicket; +#if !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(WOLFSSL_NO_SERVER) + +/* Data passed to default SessionTicket enc/dec callback. */ +typedef struct TicketEncCbCtx { + /* Name for this context. */ + byte name[WOLFSSL_TICKET_NAME_SZ]; + /* Current keys - current and next. */ + byte key[2][WOLFSSL_TICKET_KEY_SZ]; + /* Expirary date of keys. */ + word32 expirary[2]; + /* Random number generator to use for generating name, keys and IV. */ + WC_RNG rng; +#ifndef SINGLE_THREADED + /* Mutex for access to changing keys. */ + wolfSSL_Mutex mutex; +#endif + /* Pointer back to SSL_CTX. */ + WOLFSSL_CTX* ctx; +} TicketEncCbCtx; + +#endif /* !WOLFSSL_NO_DEF_TICKET_ENC_CB && !WOLFSSL_NO_SERVER */ + WOLFSSL_LOCAL int TLSX_UseSessionTicket(TLSX** extensions, SessionTicket* ticket, void* heap); WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime, @@ -2868,6 +2919,9 @@ struct WOLFSSL_CTX { SessionTicketEncCb ticketEncCb; /* enc/dec session ticket Cb */ void* ticketEncCtx; /* session encrypt context */ int ticketHint; /* ticket hint in seconds */ + #ifndef WOLFSSL_NO_DEF_TICKET_ENC_CB + TicketEncCbCtx ticketKeyCtx; + #endif #endif #ifdef HAVE_SUPPORTED_CURVES byte userCurves; /* indicates user called wolfSSL_CTX_UseSupportedCurve */ diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index d72fc7e53..50fb78557 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -1069,6 +1069,9 @@ wolfSSL_X509_STORE_set_verify_cb((WOLFSSL_X509_STORE *)(s), (WOLFSSL_X509_STORE_ #define SSL_get_tlsext_status_exts wolfSSL_get_tlsext_status_exts +#define SSL_CTX_get_tlsext_ticket_keys wolfSSL_CTX_get_tlsext_ticket_keys +#define SSL_CTX_set_tlsext_ticket_keys wolfSSL_CTX_set_tlsext_ticket_keys + #define SSL_CTRL_CLEAR_NUM_RENEGOTIATIONS 11 #define SSL_CTRL_GET_TOTAL_RENEGOTIATIONS 12 #define SSL_CTRL_SET_TMP_DH 3 diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 449470f5b..ea4b39f24 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3193,6 +3193,22 @@ WOLFSSL_API long wolfSSL_SSL_get_secure_renegotiation_support(WOLFSSL* ssl); /* Session Ticket */ #ifdef HAVE_SESSION_TICKET +#if !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(WOLFSSL_NO_SERVER) + #if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) && \ + !defined(WOLFSSL_TICKET_ENC_AES128_GCM) && \ + !defined(WOLFSSL_TICKET_ENC_AES256_GCM) + #define WOLFSSL_TICKET_KEY_SZ CHACHA20_POLY1305_AEAD_KEYSIZE + #elif defined(WOLFSSL_TICKET_ENC_AES256_GCM) + #define WOLFSSL_TICKET_KEY_SZ AES_256_KEY_SIZE + #else + #define WOLFSSL_TICKET_KEY_SZ AES_128_KEY_SIZE + #endif + + #define WOLFSSL_TICKET_KEYS_SZ (WOLFSSL_TICKET_NAME_SZ + \ + 2 * WOLFSSL_TICKET_KEY_SZ + \ + sizeof(word32) * 2) +#endif + #ifndef NO_WOLFSSL_CLIENT WOLFSSL_API int wolfSSL_UseSessionTicket(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_CTX_UseSessionTicket(WOLFSSL_CTX* ctx); @@ -3987,6 +4003,14 @@ WOLFSSL_API int PEM_write_bio_WOLFSSL_X509(WOLFSSL_BIO *bio, #endif /* OPENSSL_ALL || WOLFSSL_NGINX || WOLFSSL_HAPROXY || OPENSSL_EXTRA || HAVE_LIGHTY */ +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_WOLFSSL_SERVER) +WOLFSSL_API long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, + unsigned char *keys, int keylen); +WOLFSSL_API long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, + unsigned char *keys, int keylen); +#endif + WOLFSSL_API void wolfSSL_get0_alpn_selected(const WOLFSSL *ssl, const unsigned char **data, unsigned int *len); WOLFSSL_API int wolfSSL_select_next_proto(unsigned char **out, diff --git a/wolfssl/test.h b/wolfssl/test.h index 0a11a3a63..a28be6fc4 100644 --- a/wolfssl/test.h +++ b/wolfssl/test.h @@ -3926,7 +3926,7 @@ static WC_INLINE const char* mymktemp(char *tempfn, int len, int num) -#if defined(HAVE_SESSION_TICKET) && \ +#if defined(HAVE_SESSION_TICKET) && defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ ((defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) || \ defined(HAVE_AESGCM))