static int tls_decrypt_ticket(SSL *s, const unsigned char *etick, int eticklen, const unsigned char *sess_id, int sesslen, SSL_SESSION **psess) { SSL_SESSION *sess; unsigned char *sdec; const unsigned char *p; int slen, mlen, renew_ticket = 0; unsigned char tick_hmac[EVP_MAX_MD_SIZE]; HMAC_CTX hctx; EVP_CIPHER_CTX ctx; SSL_CTX *tctx = s->initial_ctx; /* Need at least keyname + iv + some encrypted data */ if (eticklen < 48) goto tickerr; /* Initialize session ticket encryption and HMAC contexts */ HMAC_CTX_init(&hctx); EVP_CIPHER_CTX_init(&ctx); if (tctx->tlsext_ticket_key_cb) { unsigned char *nctick = (unsigned char *)etick; int rv = tctx->tlsext_ticket_key_cb(s, nctick, nctick + 16, &ctx, &hctx, 0); if (rv < 0) return -1; if (rv == 0) goto tickerr; if (rv == 2) renew_ticket = 1; } else { /* Check key name matches */ if (memcmp(etick, tctx->tlsext_tick_key_name, 16)) goto tickerr; HMAC_Init_ex(&hctx, tctx->tlsext_tick_hmac_key, 16, tlsext_tick_md(), NULL); EVP_DecryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, tctx->tlsext_tick_aes_key, etick + 16); } /* Attempt to process session ticket, first conduct sanity and * integrity checks on ticket. */ mlen = HMAC_size(&hctx); eticklen -= mlen; /* Check HMAC of encrypted ticket */ HMAC_Update(&hctx, etick, eticklen); HMAC_Final(&hctx, tick_hmac, NULL); HMAC_CTX_cleanup(&hctx); if (memcmp(tick_hmac, etick + eticklen, mlen)) goto tickerr; /* Attempt to decrypt session data */ /* Move p after IV to start of encrypted ticket, update length */ p = etick + 16 + EVP_CIPHER_CTX_iv_length(&ctx); eticklen -= 16 + EVP_CIPHER_CTX_iv_length(&ctx); sdec = OPENSSL_malloc(eticklen); if (!sdec) { EVP_CIPHER_CTX_cleanup(&ctx); return -1; } EVP_DecryptUpdate(&ctx, sdec, &slen, p, eticklen); if (EVP_DecryptFinal(&ctx, sdec + slen, &mlen) <= 0) goto tickerr; slen += mlen; EVP_CIPHER_CTX_cleanup(&ctx); p = sdec; sess = d2i_SSL_SESSION(NULL, &p, slen); OPENSSL_free(sdec); if (sess) { /* The session ID if non-empty is used by some clients to * detect that the ticket has been accepted. So we copy it to * the session structure. If it is empty set length to zero * as required by standard. */ if (sesslen) memcpy(sess->session_id, sess_id, sesslen); sess->session_id_length = sesslen; *psess = sess; s->tlsext_ticket_expected = renew_ticket; return 1; } /* If session decrypt failure indicate a cache miss and set state to * send a new ticket */ tickerr: s->tlsext_ticket_expected = 1; return 0; }
/* tls_decrypt_ticket attempts to decrypt a session ticket. * * etick: points to the body of the session ticket extension. * eticklen: the length of the session tickets extenion. * sess_id: points at the session ID. * sesslen: the length of the session ID. * psess: (output) on return, if a ticket was decrypted, then this is set to * point to the resulting session. * * Returns: * -1: fatal error, either from parsing or decrypting the ticket. * 2: the ticket couldn't be decrypted. * 3: a ticket was successfully decrypted and *psess was set. * 4: same as 3, but the ticket needs to be renewed. */ static int tls_decrypt_ticket(SSL *s, const unsigned char *etick, int eticklen, const unsigned char *sess_id, int sesslen, SSL_SESSION **psess) { SSL_SESSION *sess; unsigned char *sdec; const unsigned char *p; int slen, mlen, renew_ticket = 0; unsigned char tick_hmac[EVP_MAX_MD_SIZE]; HMAC_CTX hctx; EVP_CIPHER_CTX ctx; SSL_CTX *tctx = s->initial_ctx; /* * The API guarantees EVP_MAX_IV_LENGTH bytes of space for * the iv to tlsext_ticket_key_cb(). Since the total space * required for a session cookie is never less than this, * this check isn't too strict. The exact check comes later. */ if (eticklen < 16 + EVP_MAX_IV_LENGTH) return 2; /* Initialize session ticket encryption and HMAC contexts */ HMAC_CTX_init(&hctx); EVP_CIPHER_CTX_init(&ctx); if (tctx->internal->tlsext_ticket_key_cb) { unsigned char *nctick = (unsigned char *)etick; int rv = tctx->internal->tlsext_ticket_key_cb(s, nctick, nctick + 16, &ctx, &hctx, 0); if (rv < 0) { HMAC_CTX_cleanup(&hctx); EVP_CIPHER_CTX_cleanup(&ctx); return -1; } if (rv == 0) { HMAC_CTX_cleanup(&hctx); EVP_CIPHER_CTX_cleanup(&ctx); return 2; } if (rv == 2) renew_ticket = 1; } else { /* Check key name matches */ if (timingsafe_memcmp(etick, tctx->internal->tlsext_tick_key_name, 16)) return 2; HMAC_Init_ex(&hctx, tctx->internal->tlsext_tick_hmac_key, 16, tlsext_tick_md(), NULL); EVP_DecryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, tctx->internal->tlsext_tick_aes_key, etick + 16); } /* * Attempt to process session ticket, first conduct sanity and * integrity checks on ticket. */ mlen = HMAC_size(&hctx); if (mlen < 0) { HMAC_CTX_cleanup(&hctx); EVP_CIPHER_CTX_cleanup(&ctx); return -1; } /* Sanity check ticket length: must exceed keyname + IV + HMAC */ if (eticklen <= 16 + EVP_CIPHER_CTX_iv_length(&ctx) + mlen) { HMAC_CTX_cleanup(&hctx); EVP_CIPHER_CTX_cleanup(&ctx); return 2; } eticklen -= mlen; /* Check HMAC of encrypted ticket */ if (HMAC_Update(&hctx, etick, eticklen) <= 0 || HMAC_Final(&hctx, tick_hmac, NULL) <= 0) { HMAC_CTX_cleanup(&hctx); EVP_CIPHER_CTX_cleanup(&ctx); return -1; } HMAC_CTX_cleanup(&hctx); if (timingsafe_memcmp(tick_hmac, etick + eticklen, mlen)) { EVP_CIPHER_CTX_cleanup(&ctx); return 2; } /* Attempt to decrypt session data */ /* Move p after IV to start of encrypted ticket, update length */ p = etick + 16 + EVP_CIPHER_CTX_iv_length(&ctx); eticklen -= 16 + EVP_CIPHER_CTX_iv_length(&ctx); sdec = malloc(eticklen); if (sdec == NULL || EVP_DecryptUpdate(&ctx, sdec, &slen, p, eticklen) <= 0) { free(sdec); EVP_CIPHER_CTX_cleanup(&ctx); return -1; } if (EVP_DecryptFinal_ex(&ctx, sdec + slen, &mlen) <= 0) { free(sdec); EVP_CIPHER_CTX_cleanup(&ctx); return 2; } slen += mlen; EVP_CIPHER_CTX_cleanup(&ctx); p = sdec; sess = d2i_SSL_SESSION(NULL, &p, slen); free(sdec); if (sess) { /* The session ID, if non-empty, is used by some clients to * detect that the ticket has been accepted. So we copy it to * the session structure. If it is empty set length to zero * as required by standard. */ if (sesslen) memcpy(sess->session_id, sess_id, sesslen); sess->session_id_length = sesslen; *psess = sess; if (renew_ticket) return 4; else return 3; } ERR_clear_error(); /* For session parse failure, indicate that we need to send a new * ticket. */ return 2; }
static int ssl_encrypt_ticket_with_cipher_ctx(SSL *ssl, CBB *out, const uint8_t *session_buf, size_t session_len) { int ret = 0; EVP_CIPHER_CTX ctx; EVP_CIPHER_CTX_init(&ctx); HMAC_CTX hctx; HMAC_CTX_init(&hctx); /* If the session is too long, emit a dummy value rather than abort the * connection. */ static const size_t kMaxTicketOverhead = 16 + EVP_MAX_IV_LENGTH + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE; if (session_len > 0xffff - kMaxTicketOverhead) { static const char kTicketPlaceholder[] = "TICKET TOO LARGE"; if (CBB_add_bytes(out, (const uint8_t *)kTicketPlaceholder, strlen(kTicketPlaceholder))) { ret = 1; } goto err; } /* Initialize HMAC and cipher contexts. If callback present it does all the * work otherwise use generated values from parent ctx. */ SSL_CTX *tctx = ssl->session_ctx; uint8_t iv[EVP_MAX_IV_LENGTH]; uint8_t key_name[16]; if (tctx->tlsext_ticket_key_cb != NULL) { if (tctx->tlsext_ticket_key_cb(ssl, key_name, iv, &ctx, &hctx, 1 /* encrypt */) < 0) { goto err; } } else { if (!RAND_bytes(iv, 16) || !EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, tctx->tlsext_tick_aes_key, iv) || !HMAC_Init_ex(&hctx, tctx->tlsext_tick_hmac_key, 16, tlsext_tick_md(), NULL)) { goto err; } OPENSSL_memcpy(key_name, tctx->tlsext_tick_key_name, 16); } uint8_t *ptr; if (!CBB_add_bytes(out, key_name, 16) || !CBB_add_bytes(out, iv, EVP_CIPHER_CTX_iv_length(&ctx)) || !CBB_reserve(out, &ptr, session_len + EVP_MAX_BLOCK_LENGTH)) { goto err; } size_t total = 0; #if defined(BORINGSSL_UNSAFE_FUZZER_MODE) OPENSSL_memcpy(ptr, session_buf, session_len); total = session_len; #else int len; if (!EVP_EncryptUpdate(&ctx, ptr + total, &len, session_buf, session_len)) { goto err; } total += len; if (!EVP_EncryptFinal_ex(&ctx, ptr + total, &len)) { goto err; } total += len; #endif if (!CBB_did_write(out, total)) { goto err; } unsigned hlen; if (!HMAC_Update(&hctx, CBB_data(out), CBB_len(out)) || !CBB_reserve(out, &ptr, EVP_MAX_MD_SIZE) || !HMAC_Final(&hctx, ptr, &hlen) || !CBB_did_write(out, hlen)) { goto err; } ret = 1; err: EVP_CIPHER_CTX_cleanup(&ctx); HMAC_CTX_cleanup(&hctx); return ret; }