diff options
Diffstat (limited to 'security/nss/lib/ssl/tls13hashstate.c')
-rw-r--r-- | security/nss/lib/ssl/tls13hashstate.c | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/security/nss/lib/ssl/tls13hashstate.c b/security/nss/lib/ssl/tls13hashstate.c new file mode 100644 index 0000000000..d14e325580 --- /dev/null +++ b/security/nss/lib/ssl/tls13hashstate.c @@ -0,0 +1,332 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is PRIVATE to SSL. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "pk11func.h" +#include "ssl.h" +#include "sslt.h" +#include "sslimpl.h" +#include "selfencrypt.h" +#include "tls13con.h" +#include "tls13ech.h" +#include "tls13err.h" +#include "tls13hashstate.h" + +/* + * The cookie is structured as a self-encrypted structure with the + * inner value being. + * + * struct { + * uint8 indicator = 0xff; // To disambiguate from tickets. + * uint16 cipherSuite; // Selected cipher suite. + * uint16 keyShare; // Requested key share group (0=none) + * PRUint8 echConfigId; // ECH config_id + * HpkeKdfId kdfId; // ECH KDF (uint16) + * HpkeAeadId aeadId; // ECH AEAD (uint16) + * opaque echHpkeCtx<0..65535>; // ECH serialized HPKE context + * opaque applicationToken<0..65535>; // Application token + * opaque ch_hash[rest_of_buffer]; // H(ClientHello) + * } CookieInner; + * + * An empty echConfigId means that ECH was not offered in the first ClientHello. + * An empty echHrrPsk means that ECH was not accepted in CH1. + */ +SECStatus +tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup, + const PRUint8 *appToken, unsigned int appTokenLen, + PRUint8 *buf, unsigned int *len, unsigned int maxlen) +{ + SECStatus rv; + SSL3Hashes hashes; + PRUint8 cookie[1024]; + sslBuffer cookieBuf = SSL_BUFFER(cookie); + static const PRUint8 indicator = 0xff; + SECItem *echHpkeCtx = NULL; + + /* Encode header. */ + rv = sslBuffer_Append(&cookieBuf, &indicator, 1); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendNumber(&cookieBuf, ss->ssl3.hs.cipher_suite, 2); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendNumber(&cookieBuf, + selectedGroup ? selectedGroup->name : 0, 2); + if (rv != SECSuccess) { + return SECFailure; + } + + if (ss->xtnData.ech) { + /* Record that we received ECH. See sslEchCookieData */ + rv = sslBuffer_AppendNumber(&cookieBuf, PR_TRUE, 1); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = sslBuffer_AppendNumber(&cookieBuf, ss->xtnData.ech->configId, + 1); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = sslBuffer_AppendNumber(&cookieBuf, ss->xtnData.ech->kdfId, 2); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendNumber(&cookieBuf, ss->xtnData.ech->aeadId, 2); + if (rv != SECSuccess) { + return SECFailure; + } + /* We need to send a ECH HRR Extension containing a signal for the client, + * we must store the signal in the cookie so we can reconstruct the transcript + * later. To avoid leaking whether ECH was accepted in the length of the cookie + * we include the empty signal in the cookie regardless. + */ + PR_ASSERT(SSL_BUFFER_LEN(&ss->ssl3.hs.greaseEchBuf) == TLS13_ECH_SIGNAL_LEN); + rv = sslBuffer_AppendBuffer(&cookieBuf, &ss->ssl3.hs.greaseEchBuf); + if (rv != SECSuccess) { + return SECFailure; + } + + /* There might be no HPKE Context, e.g. when we lack a matching ECHConfig. */ + if (ss->ssl3.hs.echHpkeCtx) { + rv = PK11_HPKE_ExportContext(ss->ssl3.hs.echHpkeCtx, NULL, &echHpkeCtx); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_AppendVariable(&cookieBuf, echHpkeCtx->data, echHpkeCtx->len, 2); + SECITEM_ZfreeItem(echHpkeCtx, PR_TRUE); + } else { + /* Zero length HPKE context. */ + rv = sslBuffer_AppendNumber(&cookieBuf, 0, 2); + } + if (rv != SECSuccess) { + return SECFailure; + } + } else { + rv = sslBuffer_AppendNumber(&cookieBuf, PR_FALSE, 1); + if (rv != SECSuccess) { + return SECFailure; + } + } + + /* Application token. */ + rv = sslBuffer_AppendVariable(&cookieBuf, appToken, appTokenLen, 2); + if (rv != SECSuccess) { + return SECFailure; + } + + /* Compute and encode hashes. */ + rv = tls13_ComputeHandshakeHashes(ss, &hashes); + if (rv != SECSuccess) { + return SECFailure; + } + rv = sslBuffer_Append(&cookieBuf, hashes.u.raw, hashes.len); + if (rv != SECSuccess) { + return SECFailure; + } + + /* Encrypt right into the buffer. */ + rv = ssl_SelfEncryptProtect(ss, cookieBuf.buf, cookieBuf.len, + buf, len, maxlen); + if (rv != SECSuccess) { + return SECFailure; + } + + return SECSuccess; +} + +/* Given a cookie and cookieLen, decrypt and parse, returning + * any values that were requested via the "previous_" params. If + * recoverState is true, the transcript state and application + * token are restored. Note that previousEchKdfId, previousEchAeadId, + * previousEchConfigId, and previousEchHpkeCtx are not modified if ECH was not + * previously negotiated (i.e., previousEchOffered is PR_FALSE). */ +SECStatus +tls13_HandleHrrCookie(sslSocket *ss, + unsigned char *cookie, unsigned int cookieLen, + ssl3CipherSuite *previousCipherSuite, + const sslNamedGroupDef **previousGroup, + PRBool *previousOfferedEch, + sslEchCookieData *echData, + PRBool recoverState) +{ + SECStatus rv; + unsigned char plaintext[1024]; + unsigned int plaintextLen = 0; + sslBuffer messageBuf = SSL_BUFFER_EMPTY; + sslReadBuffer echHpkeBuf = { 0 }; + PRBool receivedEch; + PRUint64 sentinel; + PRUint64 cipherSuite; + sslEchCookieData parsedEchData = { 0 }; + sslReadBuffer greaseReadBuf = { 0 }; + PRUint64 group; + PRUint64 tmp64; + const sslNamedGroupDef *selectedGroup; + PRUint64 appTokenLen; + + rv = ssl_SelfEncryptUnprotect(ss, cookie, cookieLen, + plaintext, &plaintextLen, sizeof(plaintext)); + if (rv != SECSuccess) { + SSL_TRC(100, ("Error decrypting cookie.")); + return SECFailure; + } + + sslReader reader = SSL_READER(plaintext, plaintextLen); + + /* Should start with the sentinel value. */ + rv = sslRead_ReadNumber(&reader, 1, &sentinel); + if ((rv != SECSuccess) || (sentinel != TLS13_COOKIE_SENTINEL)) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + /* The cipher suite should be the same or there are some shenanigans. */ + rv = sslRead_ReadNumber(&reader, 2, &cipherSuite); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + + /* The named group, if any. */ + rv = sslRead_ReadNumber(&reader, 2, &group); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + selectedGroup = ssl_LookupNamedGroup(group); + + /* Was ECH received. */ + rv = sslRead_ReadNumber(&reader, 1, &tmp64); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + receivedEch = tmp64 == PR_TRUE; + *previousOfferedEch = receivedEch; + if (receivedEch) { + /* ECH config ID */ + rv = sslRead_ReadNumber(&reader, 1, &tmp64); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + parsedEchData.configId = (PRUint8)tmp64; + + /* ECH Ciphersuite */ + rv = sslRead_ReadNumber(&reader, 2, &tmp64); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + parsedEchData.kdfId = (HpkeKdfId)tmp64; + + rv = sslRead_ReadNumber(&reader, 2, &tmp64); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + parsedEchData.aeadId = (HpkeAeadId)tmp64; + + /* ECH accept_confirmation signal. */ + rv = sslRead_Read(&reader, TLS13_ECH_SIGNAL_LEN, &greaseReadBuf); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + PORT_Memcpy(parsedEchData.signal, greaseReadBuf.buf, TLS13_ECH_SIGNAL_LEN); + + /* ECH HPKE context may be empty. */ + rv = sslRead_ReadVariable(&reader, 2, &echHpkeBuf); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + if (echData && echHpkeBuf.len) { + const SECItem hpkeItem = { siBuffer, CONST_CAST(unsigned char, echHpkeBuf.buf), + echHpkeBuf.len }; + parsedEchData.hpkeCtx = PK11_HPKE_ImportContext(&hpkeItem, NULL); + if (!parsedEchData.hpkeCtx) { + FATAL_ERROR(ss, PORT_GetError(), illegal_parameter); + return SECFailure; + } + } + } + + /* Application token. */ + rv = sslRead_ReadNumber(&reader, 2, &appTokenLen); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + sslReadBuffer appTokenReader = { 0 }; + rv = sslRead_Read(&reader, appTokenLen, &appTokenReader); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + PORT_Assert(appTokenReader.len == appTokenLen); + + if (recoverState) { + PORT_Assert(ss->xtnData.applicationToken.len == 0); + if (SECITEM_AllocItem(NULL, &ss->xtnData.applicationToken, + appTokenLen) == NULL) { + FATAL_ERROR(ss, PORT_GetError(), internal_error); + return SECFailure; + } + PORT_Memcpy(ss->xtnData.applicationToken.data, appTokenReader.buf, appTokenLen); + ss->xtnData.applicationToken.len = appTokenLen; + + /* The remainder is the hash. */ + unsigned int hashLen = SSL_READER_REMAINING(&reader); + if (hashLen != tls13_GetHashSize(ss)) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter); + return SECFailure; + } + + /* Now reinject the message. */ + SSL_ASSERT_HASHES_EMPTY(ss); + rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_message_hash, 0, + SSL_READER_CURRENT(&reader), hashLen, + ssl3_UpdateHandshakeHashes); + if (rv != SECSuccess) { + return SECFailure; + } + + /* And finally reinject the HRR. */ + rv = tls13_ConstructHelloRetryRequest(ss, cipherSuite, + selectedGroup, + cookie, cookieLen, + parsedEchData.signal, + &messageBuf); + if (rv != SECSuccess) { + return SECFailure; + } + + rv = ssl_HashHandshakeMessageInt(ss, ssl_hs_server_hello, 0, + SSL_BUFFER_BASE(&messageBuf), + SSL_BUFFER_LEN(&messageBuf), + ssl3_UpdateHandshakeHashes); + sslBuffer_Clear(&messageBuf); + if (rv != SECSuccess) { + return SECFailure; + } + } + + if (previousCipherSuite) { + *previousCipherSuite = cipherSuite; + } + if (previousGroup) { + *previousGroup = selectedGroup; + } + if (echData) { + PORT_Memcpy(echData, &parsedEchData, sizeof(parsedEchData)); + } + return SECSuccess; +} |