/*
 * Copyright 2024-2026 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the OpenSSL license (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

#include <openssl/ssl.h>
#include <openssl/ech.h>
#include "internal/ssl_unwrap.h"
#include "../ssl_local.h"

int SSL_CTX_set1_echstore(SSL_CTX *ctx, OSSL_ECHSTORE *es)
{
    if (ctx == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }
    OSSL_ECHSTORE_free(ctx->ext.ech.es);
    ctx->ext.ech.es = NULL;
    if (es == NULL)
        return 1;
    if ((ctx->ext.ech.es = ossl_echstore_dup(es)) == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
        return 0;
    }
    return 1;
}

int SSL_set1_echstore(SSL *ssl, OSSL_ECHSTORE *es)
{
    SSL_CONNECTION *s;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL)
        return 0;
    OSSL_ECHSTORE_free(s->ext.ech.es);
    s->ext.ech.es = NULL;
    if (es == NULL)
        return 1;
    if ((s->ext.ech.es = ossl_echstore_dup(es)) == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
        return 0;
    }
    /*
     * Here, and below, if the application calls an API that implies it
     * wants to try ECH, then we set attempted to 1
     */
    s->ext.ech.attempted = 1;
    return 1;
}

OSSL_ECHSTORE *SSL_CTX_get1_echstore(const SSL_CTX *ctx)
{
    OSSL_ECHSTORE *dup = NULL;

    if (ctx == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
        return NULL;
    }
    if (ctx->ext.ech.es == NULL)
        return NULL;
    if ((dup = ossl_echstore_dup(ctx->ext.ech.es)) == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
        return NULL;
    }
    return dup;
}

OSSL_ECHSTORE *SSL_get1_echstore(const SSL *ssl)
{
    SSL_CONNECTION *s;
    OSSL_ECHSTORE *dup = NULL;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
        return NULL;
    }
    if (s->ext.ech.es == NULL)
        return NULL;
    if ((dup = ossl_echstore_dup(s->ext.ech.es)) == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
        return NULL;
    }
    return dup;
}

int SSL_ech_set1_server_names(SSL *ssl, const char *inner_name,
    const char *outer_name, int no_outer)
{
    SSL_CONNECTION *s;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL)
        return 0;
    OPENSSL_free(s->ext.hostname);
    s->ext.hostname = NULL;
    if (inner_name != NULL) {
        s->ext.hostname = OPENSSL_strdup(inner_name);
        if (s->ext.hostname == NULL)
            return 0;
    }
    OPENSSL_free(s->ext.ech.outer_hostname);
    s->ext.ech.outer_hostname = NULL;
    if (no_outer == 0 && outer_name != NULL && strlen(outer_name) > 0) {
        s->ext.ech.outer_hostname = OPENSSL_strdup(outer_name);
        if (s->ext.ech.outer_hostname == NULL)
            return 0;
    }
    s->ext.ech.no_outer = no_outer;
    s->ext.ech.attempted = 1;
    return 1;
}

int SSL_ech_set1_outer_server_name(SSL *ssl, const char *outer_name,
    int no_outer)
{
    SSL_CONNECTION *s;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL)
        return 0;
    OPENSSL_free(s->ext.ech.outer_hostname);
    s->ext.ech.outer_hostname = NULL;
    if (no_outer == 0 && outer_name != NULL && strlen(outer_name) > 0) {
        s->ext.ech.outer_hostname = OPENSSL_strdup(outer_name);
        if (s->ext.ech.outer_hostname == NULL)
            return 0;
    }
    s->ext.ech.no_outer = no_outer;
    s->ext.ech.attempted = 1;
    return 1;
}

/*
 * Note that this function returns 1 for success and 0 for error. This
 * contrasts with SSL_set1_alpn_protos() which (unusually for OpenSSL)
 * returns 0 for success and 1 on error.
 */
int SSL_ech_set1_outer_alpn_protos(SSL *ssl, const unsigned char *protos,
    const size_t protos_len)
{
    SSL_CONNECTION *s;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL)
        return 0;
    OPENSSL_free(s->ext.ech.alpn_outer);
    s->ext.ech.alpn_outer = NULL;
    if (protos == NULL)
        return 1;
    if (protos_len == 0) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }
    s->ext.ech.alpn_outer = OPENSSL_memdup(protos, protos_len);
    if (s->ext.ech.alpn_outer == NULL)
        return 0;
    s->ext.ech.alpn_outer_len = protos_len;
    s->ext.ech.attempted = 1;
    return 1;
}

int SSL_ech_get1_status(SSL *ssl, char **inner_sni, char **outer_sni)
{
    char *sinner = NULL;
    char *souter = NULL;
    SSL_CONNECTION *s = SSL_CONNECTION_FROM_SSL(ssl);

    if (s == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
        return SSL_ECH_STATUS_FAILED;
    }
    if (outer_sni == NULL || inner_sni == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
        return SSL_ECH_STATUS_FAILED;
    }
    *outer_sni = NULL;
    *inner_sni = NULL;
    if (s->ext.ech.grease == OSSL_ECH_IS_GREASE) {
        if (s->ext.ech.returned != NULL)
            return SSL_ECH_STATUS_GREASE_ECH;
        return SSL_ECH_STATUS_GREASE;
    }
    if ((s->options & SSL_OP_ECH_GREASE) != 0 && s->ext.ech.attempted != 1)
        return SSL_ECH_STATUS_GREASE;
    if (s->ext.ech.backend == 1) {
        if (s->ext.hostname != NULL
            && (*inner_sni = OPENSSL_strdup(s->ext.hostname)) == NULL) {
            ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
            return SSL_ECH_STATUS_FAILED;
        }
        return SSL_ECH_STATUS_BACKEND;
    }
    if (s->ext.ech.es == NULL)
        return SSL_ECH_STATUS_NOT_CONFIGURED;
    /* Set output vars - note we may be pointing to NULL which is fine */
    if (s->server == 0) {
        sinner = s->ext.hostname;
        if (s->ext.ech.attempted == 1 && s->ext.ech.success == 0)
            sinner = s->ext.ech.former_inner;
        if (s->ext.ech.no_outer == 0)
            souter = s->ext.ech.outer_hostname;
        else
            souter = NULL;
    } else {
        if (s->ext.ech.es != NULL && s->ext.ech.success == 1) {
            sinner = s->ext.hostname;
            souter = s->ext.ech.outer_hostname;
        }
    }
    if (s->ext.ech.es != NULL && s->ext.ech.attempted == 1
        && s->ext.ech.attempted_type == TLSEXT_TYPE_ech
        && s->ext.ech.grease != OSSL_ECH_IS_GREASE) {
        long vr = X509_V_OK;

        vr = SSL_get_verify_result(ssl);
        if (sinner != NULL
            && (*inner_sni = OPENSSL_strdup(sinner)) == NULL) {
            ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
            return SSL_ECH_STATUS_FAILED;
        }
        if (souter != NULL
            && (*outer_sni = OPENSSL_strdup(souter)) == NULL) {
            ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
            return SSL_ECH_STATUS_FAILED;
        }
        if (s->ext.ech.success == 1) {
            if (vr == X509_V_OK)
                return SSL_ECH_STATUS_SUCCESS;
            else
                return SSL_ECH_STATUS_BAD_NAME;
        } else {
            if (vr == X509_V_OK && s->ext.ech.returned != NULL)
                return SSL_ECH_STATUS_FAILED_ECH;
            else if (vr != X509_V_OK && s->ext.ech.returned != NULL)
                return SSL_ECH_STATUS_FAILED_ECH_BAD_NAME;
            ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
            return SSL_ECH_STATUS_FAILED;
        }
    }
    return SSL_ECH_STATUS_NOT_TRIED;
}

int SSL_ech_set1_grease_suite(SSL *ssl, const char *suite)
{
    SSL_CONNECTION *s;
    OSSL_HPKE_SUITE hpke_suite_in = OSSL_HPKE_SUITE_DEFAULT;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL)
        return 0;
    /* check suite makes sense */
    if (OSSL_HPKE_str2suite(suite, &hpke_suite_in) != 1) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
        return 0;
    }
    OPENSSL_free(s->ext.ech.grease_suite);
    s->ext.ech.grease_suite = NULL;
    if (suite == NULL)
        return 1;
    s->ext.ech.grease_suite = OPENSSL_strdup(suite);
    if (s->ext.ech.grease_suite == NULL)
        return 0;
    s->ext.ech.grease = OSSL_ECH_IS_GREASE;
    return 1;
}

int SSL_ech_set_grease_type(SSL *ssl, uint16_t type)
{
    SSL_CONNECTION *s;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL)
        return 0;
    s->ext.ech.attempted_type = type;
    s->ext.ech.grease = OSSL_ECH_IS_GREASE;
    return 1;
}

void SSL_ech_set_callback(SSL *ssl, SSL_ech_cb_func f)
{
    SSL_CONNECTION *s;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL)
        return;
    s->ext.ech.cb = f;
    return;
}

int SSL_ech_get1_retry_config(SSL *ssl, unsigned char **ec, size_t *eclen)
{
    SSL_CONNECTION *s;
    OSSL_ECHSTORE *ve = NULL;
    BIO *in = NULL;
    int rv = 0;
    OSSL_LIB_CTX *libctx = NULL;
    const char *propq = NULL;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL || ec == NULL || eclen == NULL
        || s->ext.ech.returned_len > INT_MAX) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
        goto err;
    }
    if (s->ext.ech.returned == NULL) {
        *ec = NULL;
        *eclen = 0;
        return 1;
    }
    /*
     * before returning retry-configs check we're in a good
     * state - either the session has worked, or else it
     * failed but ECH was the only failure (we only set the
     * retry_configs_ok flag when all else worked and we're
     * about to send the ECH required alert)
     */
    if (SSL_is_init_finished(ssl) != 1 && s->ext.ech.retry_configs_ok != 1) {
        ERR_raise(ERR_LIB_SSL, ERR_R_OSSL_STORE_LIB);
        goto err;
    }
    /*
     * To not hand rubbish to application, we'll decode the value we have
     * so only syntactically good things are passed up. We won't insist
     * though that every entry in the retry_config list seems good - it
     * could be that e.g. one is a newer version than we support now,
     * and letting the application see that might cause someone to do an
     * upgrade.
     */
    if (s->ext.ech.es != NULL) {
        libctx = s->ext.ech.es->libctx;
        propq = s->ext.ech.es->propq;
    }
    if ((in = BIO_new(BIO_s_mem())) == NULL
        || BIO_write(in, s->ext.ech.returned, (int)s->ext.ech.returned_len) <= 0
        || (ve = OSSL_ECHSTORE_new(libctx, propq)) == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
        goto err;
    }
    if (OSSL_ECHSTORE_read_echconfiglist(ve, in) != 1) {
        ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
        goto err;
    }
    /* all good, copy and return */
    *ec = OPENSSL_memdup(s->ext.ech.returned, s->ext.ech.returned_len);
    if (*ec == NULL)
        goto err;
    *eclen = s->ext.ech.returned_len;
    rv = 1;
err:
    OSSL_ECHSTORE_free(ve);
    BIO_free_all(in);
    return rv;
}

/*
 * Note that this function returns 1 for success and 0 for error. This
 * contrasts with SSL_CTX_set1_alpn_protos() which (unusually for OpenSSL)
 * returns 0 for success and 1 on error.
 */
int SSL_CTX_ech_set1_outer_alpn_protos(SSL_CTX *ctx,
    const unsigned char *protos,
    const size_t protos_len)
{
    if (ctx == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }
    OPENSSL_free(ctx->ext.ech.alpn_outer);
    ctx->ext.ech.alpn_outer = NULL;
    if (protos == NULL)
        return 1;
    if (protos_len == 0) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
        return 0;
    }
    ctx->ext.ech.alpn_outer = OPENSSL_memdup(protos, protos_len);
    if (ctx->ext.ech.alpn_outer == NULL)
        return 0;
    ctx->ext.ech.alpn_outer_len = protos_len;
    return 1;
}

void SSL_CTX_ech_set_callback(SSL_CTX *ctx, SSL_ech_cb_func f)
{
    if (ctx == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
        return;
    }
    ctx->ext.ech.cb = f;
    return;
}

int SSL_set1_ech_config_list(SSL *ssl, const uint8_t *ecl, size_t ecl_len)
{
    int rv = 0;
    SSL_CONNECTION *s;
    OSSL_ECHSTORE *es = NULL;
    BIO *es_in = NULL;

    s = SSL_CONNECTION_FROM_SSL(ssl);
    if (s == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
        goto err;
    }
    if (ecl == NULL) {
        OSSL_ECHSTORE_free(s->ext.ech.es);
        s->ext.ech.es = NULL;
        return 1;
    }
    if (ecl_len == 0 || ecl_len > INT_MAX) {
        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
        goto err;
    }
    if ((es_in = BIO_new_mem_buf(ecl, (int)ecl_len)) == NULL
        || (es = OSSL_ECHSTORE_new(NULL, NULL)) == NULL
        || OSSL_ECHSTORE_read_echconfiglist(es, es_in) != 1
        || SSL_set1_echstore(ssl, es) != 1) {
        ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
        goto err;
    }
    rv = 1;
err:
    OSSL_ECHSTORE_free(es);
    BIO_free_all(es_in);
    return rv;
}
