/* -*- 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; }