openssl/ssl/ech/ech_ssl_apis.c

433 lines
13 KiB
C

/*
* Copyright 2024 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;
s = SSL_CONNECTION_FROM_SSL(ssl);
if (s == NULL)
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.attempted = 1;
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.attempted = 1;
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)
goto err;
if (s->ext.ech.returned == NULL) {
*ec = NULL;
*eclen = 0;
return 1;
}
/*
* 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, 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_NULL_PARAMETER);
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;
}
int SSL_CTX_ech_raw_decrypt(SSL_CTX *ctx,
int *decrypted_ok,
char **inner_sni, char **outer_sni,
unsigned char *outer_ch, size_t outer_len,
unsigned char *inner_ch, size_t *inner_len,
unsigned char **hrrtok, size_t *toklen)
{
if (ctx == NULL) {
/*
* TODO(ECH): this is a bit of a bogus error, just so as
* to get the `make update` command to add the required
* error number. We don't need it yet, but it's involved
* in some of the build artefacts, so may as well jump
* the gun a bit on it.
*/
ERR_raise(ERR_LIB_SSL, SSL_R_ECH_REQUIRED);
return 0;
}
return 0;
}
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) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
if ((es_in = BIO_new_mem_buf(ecl, 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;
}