/* -*- 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;
    sslBuffer greaseBuf = SSL_BUFFER_EMPTY;

    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;

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

        if (echData) {
            rv = sslBuffer_Append(&greaseBuf, greaseReadBuf.buf, greaseReadBuf.len);
            if (rv != SECSuccess) {
                FATAL_ERROR(ss, SSL_ERROR_INTERNAL_ERROR_ALERT, internal_error);
                return SECFailure;
            }
            parsedEchData.signal = greaseBuf;
        }

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