mirror of
https://github.com/openssl/openssl.git
synced 2026-05-07 20:12:39 +00:00
s_client and s_server options for ECH
Reviewed-by: Matt Caswell <matt@openssl.org> Reviewed-by: Tomas Mraz <tomas@openssl.org> (Merged from https://github.com/openssl/openssl/pull/28270)
This commit is contained in:
@@ -14,6 +14,7 @@ ignore-words-list =
|
||||
ADDAD,
|
||||
addin,
|
||||
adin,
|
||||
ADn,
|
||||
AFAIR,
|
||||
afile,
|
||||
afterAll,
|
||||
|
||||
+189
-30
@@ -108,7 +108,12 @@ static BIO *bio_c_out = NULL;
|
||||
static int c_quiet = 0;
|
||||
static char *sess_out = NULL;
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
static char *ech_config_list = NULL;
|
||||
static char *ech_config_list = NULL, *ech_grease_suite = NULL;
|
||||
static const char *sni_outer_name = NULL;
|
||||
static int ech_grease = 0, ech_ignore_cid = 0;
|
||||
static int ech_select = OSSL_ECHSTORE_ALL;
|
||||
static int ech_grease_type = OSSL_ECH_CURRENT_VERSION;
|
||||
static int ech_no_outer_sni = 0;
|
||||
#endif
|
||||
static SSL_SESSION *psksess = NULL;
|
||||
|
||||
@@ -613,6 +618,14 @@ typedef enum OPTION_choice {
|
||||
OPT_KTLS,
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
OPT_ECHCONFIGLIST,
|
||||
OPT_SNIOUTER,
|
||||
OPT_ALPN_OUTER,
|
||||
OPT_ECH_SELECT,
|
||||
OPT_ECH_IGNORE_CONFIG_ID,
|
||||
OPT_ECH_GREASE,
|
||||
OPT_ECH_GREASE_SUITE,
|
||||
OPT_ECH_GREASE_TYPE,
|
||||
OPT_ECH_NO_OUTER_SNI,
|
||||
#endif
|
||||
OPT_R_ENUM,
|
||||
OPT_PROV_ENUM
|
||||
@@ -807,14 +820,31 @@ const OPTIONS s_client_options[] = {
|
||||
{ "enable_pha", OPT_ENABLE_PHA, '-', "Enable post-handshake-authentication" },
|
||||
{ "enable_server_rpk", OPT_ENABLE_SERVER_RPK, '-', "Enable raw public keys (RFC7250) from the server" },
|
||||
{ "enable_client_rpk", OPT_ENABLE_CLIENT_RPK, '-', "Enable raw public keys (RFC7250) from the client" },
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
{ "ech_config_list", OPT_ECHCONFIGLIST, 's',
|
||||
"Set ECHConfigList, value is base 64 encoded ECHConfigList" },
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_SRTP
|
||||
{ "use_srtp", OPT_USE_SRTP, 's',
|
||||
"Offer SRTP key management with a colon-separated profile list" },
|
||||
#endif
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
{ "ech_config_list", OPT_ECHCONFIGLIST, 's',
|
||||
"Set ECHConfigList, value is base64-encoded ECHConfigList" },
|
||||
{ "ech_outer_alpn", OPT_ALPN_OUTER, 's',
|
||||
"Specify outer ALPN value, when using ECH (comma-separated list)" },
|
||||
{ "ech_outer_sni", OPT_SNIOUTER, 's',
|
||||
"The name to put in the outer CH when overriding the server's choice" },
|
||||
{ "ech_no_outer_sni", OPT_ECH_NO_OUTER_SNI, '-',
|
||||
"Do not send the server name (SNI) extension in the outer ClientHello" },
|
||||
{ "ech_select", OPT_ECH_SELECT, 'n',
|
||||
"Select one ECHConfig from the set provided via -ech_config_list" },
|
||||
{ "ech_grease", OPT_ECH_GREASE, '-',
|
||||
"Send GREASE values when not really using ECH" },
|
||||
{ "ech_grease_suite", OPT_ECH_GREASE_SUITE, 's',
|
||||
"Use this HPKE suite for GREASE values when not really using ECH" },
|
||||
{ "ech_grease_type", OPT_ECH_GREASE_TYPE, 'n',
|
||||
"Use this TLS extension type for GREASE values when not really using ECH" },
|
||||
{ "ech_ignore_cid", OPT_ECH_IGNORE_CONFIG_ID, '-',
|
||||
"Ignore the server-chosen ECH config ID and send a random value" },
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_SRP
|
||||
{ "srpuser", OPT_SRPUSER, 's', "(deprecated) SRP authentication for 'user'" },
|
||||
{ "srppass", OPT_SRPPASS, 's', "(deprecated) Password for 'user'" },
|
||||
@@ -1009,6 +1039,11 @@ int s_client_main(int argc, char **argv)
|
||||
char *sname_alloc = NULL;
|
||||
int noservername = 0;
|
||||
const char *alpn_in = NULL;
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
const char *alpn_outer_in = NULL;
|
||||
int rv = 0;
|
||||
OSSL_ECHSTORE *es = NULL;
|
||||
#endif
|
||||
tlsextctx tlsextcbp = { NULL, 0 };
|
||||
const char *ssl_config = NULL;
|
||||
#define MAX_SI_TYPES 100
|
||||
@@ -1596,6 +1631,30 @@ int s_client_main(int argc, char **argv)
|
||||
case OPT_ECHCONFIGLIST:
|
||||
ech_config_list = opt_arg();
|
||||
break;
|
||||
case OPT_ALPN_OUTER:
|
||||
alpn_outer_in = opt_arg();
|
||||
break;
|
||||
case OPT_SNIOUTER:
|
||||
sni_outer_name = opt_arg();
|
||||
break;
|
||||
case OPT_ECH_SELECT:
|
||||
ech_select = atoi(opt_arg());
|
||||
break;
|
||||
case OPT_ECH_GREASE:
|
||||
ech_grease = 1;
|
||||
break;
|
||||
case OPT_ECH_GREASE_SUITE:
|
||||
ech_grease_suite = opt_arg();
|
||||
break;
|
||||
case OPT_ECH_GREASE_TYPE:
|
||||
ech_grease_type = atoi(opt_arg());
|
||||
break;
|
||||
case OPT_ECH_IGNORE_CONFIG_ID:
|
||||
ech_ignore_cid = 1;
|
||||
break;
|
||||
case OPT_ECH_NO_OUTER_SNI:
|
||||
ech_no_outer_sni = 1;
|
||||
break;
|
||||
#endif
|
||||
case OPT_NOSERVERNAME:
|
||||
noservername = 1;
|
||||
@@ -1709,7 +1768,17 @@ int s_client_main(int argc, char **argv)
|
||||
goto opthelp;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
if ((alpn_outer_in != NULL || sni_outer_name != NULL
|
||||
|| ech_no_outer_sni == 1)
|
||||
&& ech_config_list == NULL) {
|
||||
BIO_printf(bio_err, "%s: Can't use -ech_outer_sni nor "
|
||||
"-ech_outer_alpn nor -no_ech_outer_sni without "
|
||||
"-ech_config_list\n",
|
||||
prog);
|
||||
goto opthelp;
|
||||
}
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_NEXTPROTONEG
|
||||
if (min_version == TLS1_3_VERSION && next_proto_neg_in != NULL) {
|
||||
BIO_puts(bio_err, "Cannot supply -nextprotoneg with TLSv1.3\n");
|
||||
@@ -1938,6 +2007,13 @@ int s_client_main(int argc, char **argv)
|
||||
SSL_CTX_set_options(ctx, SSL_OP_ENABLE_KTLS);
|
||||
#endif
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
if (ech_grease != 0)
|
||||
SSL_CTX_set_options(ctx, SSL_OP_ECH_GREASE);
|
||||
if (ech_ignore_cid != 0)
|
||||
SSL_CTX_set_options(ctx, SSL_OP_ECH_IGNORE_CID);
|
||||
#endif
|
||||
|
||||
if (vpmtouched && !SSL_CTX_set1_param(ctx, vpm)) {
|
||||
BIO_puts(bio_err, "Error setting verify params\n");
|
||||
goto end;
|
||||
@@ -2132,6 +2208,27 @@ int s_client_main(int argc, char **argv)
|
||||
if (set_keylog_file(ctx, keylog_file))
|
||||
goto end;
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
if (alpn_outer_in != NULL) {
|
||||
size_t alpn_outer_len;
|
||||
unsigned char *alpn_outer = NULL;
|
||||
|
||||
alpn_outer = next_protos_parse(&alpn_outer_len, alpn_outer_in);
|
||||
if (alpn_outer == NULL) {
|
||||
BIO_printf(bio_err, "Error parsing -ech_outer_alpn argument\n");
|
||||
goto end;
|
||||
}
|
||||
if (SSL_CTX_ech_set1_outer_alpn_protos(ctx, alpn_outer,
|
||||
alpn_outer_len)
|
||||
!= 1) {
|
||||
BIO_printf(bio_err, "Error setting ALPN-OUTER\n");
|
||||
OPENSSL_free(alpn_outer);
|
||||
goto end;
|
||||
}
|
||||
OPENSSL_free(alpn_outer);
|
||||
}
|
||||
#endif
|
||||
|
||||
con = SSL_new(ctx);
|
||||
if (con == NULL)
|
||||
goto end;
|
||||
@@ -2151,6 +2248,25 @@ int s_client_main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
if (ech_grease_suite != NULL) {
|
||||
if (SSL_ech_set1_grease_suite(con, ech_grease_suite) != 1) {
|
||||
ERR_print_errors(bio_err);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
/* no point in setting to our default */
|
||||
if (ech_grease_type != OSSL_ECH_CURRENT_VERSION) {
|
||||
BIO_printf(bio_err, "Setting GREASE ECH type 0x%4x\n", ech_grease_type);
|
||||
if (SSL_ech_set_grease_type(con, ech_grease_type) != 1) {
|
||||
BIO_printf(bio_err, "Can't set GREASE ECH type 0x%4x\n",
|
||||
ech_grease_type);
|
||||
ERR_print_errors(bio_err);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sess_in != NULL) {
|
||||
SSL_SESSION *sess;
|
||||
BIO *stmp = BIO_new_file(sess_in, "r");
|
||||
@@ -2165,6 +2281,7 @@ int s_client_main(int argc, char **argv)
|
||||
goto end;
|
||||
}
|
||||
if (!SSL_set_session(con, sess)) {
|
||||
SSL_SESSION_free(sess);
|
||||
BIO_puts(bio_err, "Can't set session\n");
|
||||
goto end;
|
||||
}
|
||||
@@ -2187,11 +2304,49 @@ int s_client_main(int argc, char **argv)
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
if (ech_config_list != NULL
|
||||
&& SSL_set1_ech_config_list(con, (unsigned char *)ech_config_list,
|
||||
strlen(ech_config_list))
|
||||
!= 1)
|
||||
goto end;
|
||||
if (ech_config_list != NULL) {
|
||||
if (SSL_set1_ech_config_list(con, (unsigned char *)ech_config_list,
|
||||
strlen(ech_config_list))
|
||||
!= 1) {
|
||||
BIO_printf(bio_err, "%s: error setting ECHConfigList.\n", prog);
|
||||
goto end;
|
||||
}
|
||||
if (ech_no_outer_sni == 1) {
|
||||
if (sni_outer_name != NULL) {
|
||||
BIO_printf(bio_err, "%s: can't set -ech_no_outer_sni and "
|
||||
"-ech_outer_sni together.\n",
|
||||
prog);
|
||||
goto end;
|
||||
}
|
||||
if (SSL_ech_set1_outer_server_name(con, NULL, 1) != 1) {
|
||||
BIO_printf(bio_err, "%s: setting no ECH outer name failed.\n",
|
||||
prog);
|
||||
ERR_print_errors(bio_err);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (sni_outer_name != NULL) {
|
||||
rv = SSL_ech_set1_outer_server_name(con, sni_outer_name, 0);
|
||||
if (rv != 1) {
|
||||
BIO_printf(bio_err, "%s: setting ECH outer name to %s failed.\n",
|
||||
prog, sni_outer_name);
|
||||
ERR_print_errors(bio_err);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ech_select != OSSL_ECHSTORE_ALL) {
|
||||
if ((es = SSL_get1_echstore(con)) == NULL
|
||||
|| OSSL_ECHSTORE_downselect(es, ech_select) != 1
|
||||
|| SSL_set1_echstore(con, es) != 1) {
|
||||
BIO_printf(bio_err, "%s: ECH downselect to (%d) failed.\n",
|
||||
prog, ech_select);
|
||||
ERR_print_errors(bio_err);
|
||||
goto end;
|
||||
}
|
||||
OSSL_ECHSTORE_free(es);
|
||||
es = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dane_tlsa_domain != NULL) {
|
||||
@@ -3390,6 +3545,9 @@ end:
|
||||
bio_c_out = NULL;
|
||||
BIO_free(bio_c_msg);
|
||||
bio_c_msg = NULL;
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
OSSL_ECHSTORE_free(es);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -3466,11 +3624,12 @@ static void print_ech_retry_configs(BIO *bio, SSL *s)
|
||||
if (OSSL_ECHSTORE_get1_info(es, ind, &secs, &pn, &ec,
|
||||
&has_priv, &for_retry)
|
||||
!= 1) {
|
||||
BIO_printf(bio, "ECH: Error getting retry-config %d\n", ind);
|
||||
BIO_printf(bio, "ECH: Error getting retry-config %d.\n", ind);
|
||||
goto end;
|
||||
}
|
||||
BIO_printf(bio, "ECH: entry: %d public_name: %s age: %d%s\n",
|
||||
ind, pn, (int)secs, has_priv ? " (has private key)" : "");
|
||||
BIO_printf(bio, "ECH: entry: %d public_name: %s age: %lld%s\n",
|
||||
ind, pn, (long long)secs,
|
||||
has_priv ? " (has private key)" : "");
|
||||
BIO_printf(bio, "ECH: \t%s\n", ec);
|
||||
OPENSSL_free(pn);
|
||||
pn = NULL;
|
||||
@@ -3486,6 +3645,7 @@ end:
|
||||
return;
|
||||
}
|
||||
|
||||
/* outcomes marked as "odd" shouldn't happen in s_client */
|
||||
static void print_ech_status(BIO *bio, SSL *s, int estat)
|
||||
{
|
||||
switch (estat) {
|
||||
@@ -3502,7 +3662,7 @@ static void print_ech_status(BIO *bio, SSL *s, int estat)
|
||||
BIO_printf(bio, "ECH: success: %d\n", estat);
|
||||
break;
|
||||
case SSL_ECH_STATUS_GREASE_ECH:
|
||||
BIO_printf(bio, "ECH: GREASE+retry-configs%d\n", estat);
|
||||
BIO_printf(bio, "ECH: GREASE+retry-configs: %d\n", estat);
|
||||
break;
|
||||
case SSL_ECH_STATUS_BACKEND:
|
||||
BIO_printf(bio, "ECH: BACKEND: %d\n", estat);
|
||||
@@ -3543,6 +3703,10 @@ static void print_stuff(BIO *bio, SSL *s, int full)
|
||||
#ifndef OPENSSL_NO_CT
|
||||
const SSL_CTX *ctx = SSL_get_SSL_CTX(s);
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
char *inner = NULL, *outer = NULL;
|
||||
int estat = 0;
|
||||
#endif
|
||||
|
||||
if (full) {
|
||||
int got_a_chain = 0;
|
||||
@@ -3773,22 +3937,17 @@ static void print_stuff(BIO *bio, SSL *s, int full)
|
||||
}
|
||||
BIO_puts(bio, "---\n");
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
{
|
||||
char *inner = NULL, *outer = NULL;
|
||||
int estat = 0;
|
||||
|
||||
estat = SSL_ech_get1_status(s, &inner, &outer);
|
||||
print_ech_status(bio, s, estat);
|
||||
if (estat == SSL_ECH_STATUS_SUCCESS) {
|
||||
BIO_printf(bio, "ECH: inner: %s\n", inner);
|
||||
BIO_printf(bio, "ECH: outer: %s\n", outer);
|
||||
}
|
||||
if (estat == SSL_ECH_STATUS_FAILED_ECH
|
||||
|| estat == SSL_ECH_STATUS_FAILED_ECH_BAD_NAME)
|
||||
print_ech_retry_configs(bio, s);
|
||||
OPENSSL_free(inner);
|
||||
OPENSSL_free(outer);
|
||||
estat = SSL_ech_get1_status(s, &inner, &outer);
|
||||
print_ech_status(bio, s, estat);
|
||||
if (estat == SSL_ECH_STATUS_SUCCESS) {
|
||||
BIO_printf(bio, "ECH: inner: %s\n", inner);
|
||||
BIO_printf(bio, "ECH: outer: %s\n", outer);
|
||||
}
|
||||
if (estat == SSL_ECH_STATUS_FAILED_ECH
|
||||
|| estat == SSL_ECH_STATUS_FAILED_ECH_BAD_NAME)
|
||||
print_ech_retry_configs(bio, s);
|
||||
OPENSSL_free(inner);
|
||||
OPENSSL_free(outer);
|
||||
BIO_puts(bio, "---\n");
|
||||
#endif
|
||||
|
||||
|
||||
+500
-1
@@ -18,6 +18,9 @@
|
||||
#if defined(_WIN32)
|
||||
/* Included before async.h to avoid some warnings */
|
||||
#include <windows.h>
|
||||
#if !defined(OPENSSL_NO_ECH) && !defined(PATH_MAX)
|
||||
#define PATH_MAX 4096
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <openssl/e_os2.h>
|
||||
@@ -26,6 +29,27 @@
|
||||
#include <openssl/decoder.h>
|
||||
#include "internal/sockets.h" /* for openssl_fdset() */
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
/* to use tracing, if configured and requested */
|
||||
#ifndef OPENSSL_NO_SSL_TRACE
|
||||
#include <openssl/trace.h>
|
||||
#endif
|
||||
/* sockaddr stuff */
|
||||
#if defined(_WIN32)
|
||||
#include <winsock.h>
|
||||
#include <ws2ipdef.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
/* for timing in some TRACE statements */
|
||||
#include <time.h>
|
||||
#include "internal/o_dir.h" /* for OPENSSL_DIR_read */
|
||||
#endif
|
||||
|
||||
#ifndef OPENSSL_NO_SOCK
|
||||
|
||||
/*
|
||||
@@ -59,6 +83,11 @@ typedef unsigned int u_int;
|
||||
#include "internal/sockets.h"
|
||||
#include "internal/statem.h"
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
/* needed for X509_check_host in some CI builds "no-http" */
|
||||
#include <openssl/x509v3.h>
|
||||
#endif
|
||||
|
||||
static int not_resumable_sess_cb(SSL *s, int is_forward_secure);
|
||||
static int sv_body(int s, int stype, int prot, unsigned char *context);
|
||||
static int www_body(int s, int stype, int prot, unsigned char *context);
|
||||
@@ -72,6 +101,10 @@ static void init_session_cache_ctx(SSL_CTX *sctx);
|
||||
static void free_sessions(void);
|
||||
static void print_connection_info(SSL *con);
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
static unsigned int ech_print_cb(SSL *s, const char *str);
|
||||
#endif
|
||||
|
||||
static const int bufsize = 16 * 1024;
|
||||
static int accept_socket = -1;
|
||||
|
||||
@@ -421,8 +454,195 @@ typedef struct tlsextctx_st {
|
||||
char *servername;
|
||||
BIO *biodebug;
|
||||
int extension_error;
|
||||
X509 *scert; /* ECH needs 2nd cert for testing */
|
||||
} tlsextctx;
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
static unsigned int ech_print_cb(SSL *s, const char *str)
|
||||
{
|
||||
if (str != NULL)
|
||||
BIO_printf(bio_s_out, "ECH Server callback printing: \n%s\n", str);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The server has possibly 2 TLS server names basically in ctx and ctx2. So we
|
||||
* need to check if any client-supplied SNI in the inner/outer matches either
|
||||
* and serve whichever is appropriate. X509_check_host is the way to do that,
|
||||
* given an X509* pointer.
|
||||
*
|
||||
* We default to the "main" ctx if the client-supplied SNI does not match the
|
||||
* ctx2 certificate. We don't fail if the client-supplied SNI matches neither,
|
||||
* but just continue with the "main" ctx. If the client-supplied SNI matches
|
||||
* both ctx and ctx2, then we'll switch to ctx2 anyway - we don't try for a
|
||||
* "best" match in that case.
|
||||
*
|
||||
* Note that since we attempt ECH decryption whenever configured to do that,
|
||||
* the only way to get the "outer" SNI is via SSL_ech_get1_status.
|
||||
*/
|
||||
|
||||
/* apparently 26 is all we need, but round it up to 32 to be on the safe side */
|
||||
#define ECH_TIME_STR_LEN 32
|
||||
|
||||
static int ssl_ech_servername_cb(SSL *s, int *ad, void *arg)
|
||||
{
|
||||
tlsextctx *p = (tlsextctx *)arg;
|
||||
time_t now = time(0); /* For a bit of basic logging */
|
||||
int sockfd = 0, res = 0, echrv = 0;
|
||||
size_t srv = 0;
|
||||
struct sockaddr_storage ss;
|
||||
socklen_t salen = sizeof(ss);
|
||||
struct sockaddr *sa;
|
||||
char clientip[INET6_ADDRSTRLEN], lstr[ECH_TIME_STR_LEN];
|
||||
const char *servername = NULL;
|
||||
char *inner_sni = NULL, *outer_sni = NULL;
|
||||
struct tm local;
|
||||
#if !defined(OPENSSL_SYS_WINDOWS)
|
||||
struct tm *local_p = NULL;
|
||||
#else
|
||||
errno_t grv;
|
||||
#endif
|
||||
|
||||
#if !defined(OPENSSL_SYS_WINDOWS)
|
||||
local_p = gmtime_r(&now, &local);
|
||||
if (local_p != &local) {
|
||||
strcpy(lstr, "sometime");
|
||||
} else {
|
||||
srv = strftime(lstr, ECH_TIME_STR_LEN, "%c", &local);
|
||||
if (srv == 0)
|
||||
strcpy(lstr, "sometime");
|
||||
}
|
||||
#else
|
||||
grv = gmtime_s(&local, &now);
|
||||
if (grv != 0) {
|
||||
strcpy(lstr, "sometime");
|
||||
} else {
|
||||
srv = strftime(lstr, ECH_TIME_STR_LEN, "%c", &local);
|
||||
if (srv == 0)
|
||||
strcpy(lstr, "sometime");
|
||||
}
|
||||
#endif
|
||||
memset(clientip, 0, INET6_ADDRSTRLEN);
|
||||
strncpy(clientip, "unknown", INET6_ADDRSTRLEN);
|
||||
memset(&ss, 0, salen);
|
||||
sa = (struct sockaddr *)&ss;
|
||||
res = BIO_get_fd(SSL_get_wbio(s), &sockfd);
|
||||
if (res != -1) {
|
||||
#if !defined(_WIN32)
|
||||
res = getpeername(sockfd, sa, &salen);
|
||||
#else
|
||||
res = getpeername(sockfd, sa, (int *)&salen);
|
||||
#endif
|
||||
if (res == 0)
|
||||
res = getnameinfo(sa, salen, clientip, INET6_ADDRSTRLEN,
|
||||
0, 0, NI_NUMERICHOST);
|
||||
}
|
||||
/* Name that matches "main" ctx */
|
||||
servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
|
||||
echrv = SSL_ech_get1_status(s, &inner_sni, &outer_sni);
|
||||
if (p->biodebug != NULL) {
|
||||
/* spit out basic logging */
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: connection from %s at %s\n",
|
||||
clientip, lstr);
|
||||
/* Client supplied SNI from inner and outer */
|
||||
switch (echrv) {
|
||||
case SSL_ECH_STATUS_BACKEND:
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: ECH backend got inner ECH\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_NOT_CONFIGURED:
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: ECH not configured\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_GREASE:
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: attempt we think is GREASE\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_NOT_TRIED:
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: not attempted\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_FAILED:
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: tried but failed\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_BAD_CALL:
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: bad input to API\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_BAD_NAME:
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: worked but bad name\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_SUCCESS:
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: success: outer %s, inner: %s\n",
|
||||
(outer_sni == NULL ? "none" : outer_sni),
|
||||
(inner_sni == NULL ? "none" : inner_sni));
|
||||
break;
|
||||
default:
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: Error getting ECH status\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
OPENSSL_free(inner_sni);
|
||||
OPENSSL_free(outer_sni);
|
||||
if (servername != NULL && p->biodebug != NULL) {
|
||||
const char *cp = servername;
|
||||
unsigned char uc;
|
||||
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: Hostname in TLS extension: \"");
|
||||
while ((uc = *cp++) != 0)
|
||||
BIO_printf(p->biodebug,
|
||||
isascii(uc) && isprint(uc) ? "%c" : "\\x%02x", uc);
|
||||
BIO_printf(p->biodebug, "\"\n");
|
||||
if (p->servername != NULL)
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: ctx servername: %s\n",
|
||||
p->servername);
|
||||
else
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: ctx servername is NULL\n");
|
||||
if (p->scert == NULL)
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: No 2nd cert! That's bad.\n");
|
||||
}
|
||||
if (p->servername == NULL)
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
if (p->scert == NULL)
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
if (echrv == SSL_ECH_STATUS_SUCCESS && servername != NULL) {
|
||||
if (ctx2 != NULL) {
|
||||
int check_host = X509_check_host(p->scert, servername, 0, 0, NULL);
|
||||
|
||||
if (check_host == 1) {
|
||||
if (p->biodebug != NULL)
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: Switching context.\n");
|
||||
SSL_set_SSL_CTX(s, ctx2);
|
||||
} else {
|
||||
if (p->biodebug != NULL)
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: Not switching context "
|
||||
"- no name match (%d).\n",
|
||||
check_host);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (p->biodebug != NULL)
|
||||
BIO_printf(p->biodebug,
|
||||
"ssl_ech_servername_cb: Not switching context "
|
||||
"- no ECH SUCCESS\n");
|
||||
}
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
/* Below is the "original" ssl_servername_cb, before ECH */
|
||||
|
||||
#else
|
||||
|
||||
static int ssl_servername_cb(SSL *s, int *ad, void *arg)
|
||||
{
|
||||
tlsextctx *p = (tlsextctx *)arg;
|
||||
@@ -453,6 +673,8 @@ static int ssl_servername_cb(SSL *s, int *ad, void *arg)
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Structure passed to cert status callback */
|
||||
typedef struct tlsextstatusctx_st {
|
||||
int timeout;
|
||||
@@ -1065,6 +1287,13 @@ typedef enum OPTION_choice {
|
||||
OPT_CERT_COMP,
|
||||
OPT_ENABLE_SERVER_RPK,
|
||||
OPT_ENABLE_CLIENT_RPK,
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
OPT_ECH_PEM,
|
||||
OPT_ECH_DIR,
|
||||
OPT_ECH_NORETRY,
|
||||
OPT_ECH_TRIALDECRYPT,
|
||||
OPT_ECH_GREASE_RT,
|
||||
#endif
|
||||
OPT_R_ENUM,
|
||||
OPT_S_ENUM,
|
||||
OPT_V_ENUM,
|
||||
@@ -1307,6 +1536,19 @@ const OPTIONS s_server_options[] = {
|
||||
#endif
|
||||
{ "alpn", OPT_ALPN, 's',
|
||||
"Set the advertised protocols for the ALPN extension (comma-separated list)" },
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
{ "ech_key", OPT_ECH_PEM, 's', "Load ECH PEM-formatted key pair" },
|
||||
{ "ech_dir", OPT_ECH_DIR, 's', "Load ECH key pairs (for retries) "
|
||||
"from the specified directory" },
|
||||
{ "ech_noretry_dir", OPT_ECH_NORETRY, 's', "Load ECH key pairs (not "
|
||||
"for retry) from the specified directory" },
|
||||
{ "ech_trialdecrypt", OPT_ECH_TRIALDECRYPT, '-',
|
||||
"Do trial decryption even if ECH record_digest matching fails" },
|
||||
{ "ech_greaseretries", OPT_ECH_GREASE_RT, '-',
|
||||
"Set server to GREASE retry_config values" },
|
||||
#endif
|
||||
|
||||
#ifndef OPENSSL_NO_KTLS
|
||||
{ "ktls", OPT_KTLS, '-', "Enable Kernel TLS for sending and receiving" },
|
||||
{ "sendfile", OPT_SENDFILE, '-', "Use sendfile to response file with -WWW" },
|
||||
@@ -1322,6 +1564,65 @@ const OPTIONS s_server_options[] = {
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
static int ech_load_dir(SSL_CTX *lctx, const char *thedir,
|
||||
int for_retry, int *nloaded)
|
||||
{
|
||||
size_t elen = strlen(thedir);
|
||||
OPENSSL_DIR_CTX *d = NULL;
|
||||
const char *thisfile = NULL;
|
||||
OSSL_ECHSTORE *es = NULL;
|
||||
BIO *in = NULL;
|
||||
int loaded = 0;
|
||||
|
||||
if ((elen + 7) >= PATH_MAX) { /* too long, go away */
|
||||
BIO_printf(bio_err, "'%s' too long - exiting\n", thedir);
|
||||
return 0;
|
||||
}
|
||||
if (app_isdir(thedir) <= 0) { /* if not a directory, ignore it */
|
||||
BIO_printf(bio_err, "'%s' not a directory - exiting\n", thedir);
|
||||
return 0;
|
||||
}
|
||||
if ((es = SSL_CTX_get1_echstore(lctx)) == NULL
|
||||
&& (es = OSSL_ECHSTORE_new(app_get0_libctx(),
|
||||
app_get0_propq()))
|
||||
== NULL) {
|
||||
BIO_printf(bio_err, "internal error\n");
|
||||
return 0;
|
||||
}
|
||||
while ((thisfile = OPENSSL_DIR_read(&d, thedir))) {
|
||||
char filepath[PATH_MAX];
|
||||
int r;
|
||||
|
||||
#ifdef OPENSSL_SYS_VMS
|
||||
r = BIO_snprintf(filepath, sizeof(filepath), "%s%s", thedir, thisfile);
|
||||
#else
|
||||
r = BIO_snprintf(filepath, sizeof(filepath), "%s/%s", thedir, thisfile);
|
||||
#endif
|
||||
if (r < 0
|
||||
|| app_isdir(filepath) > 0
|
||||
|| (in = BIO_new_file(filepath, "r")) == NULL
|
||||
|| OSSL_ECHSTORE_read_pem(es, in, for_retry) != 1) {
|
||||
BIO_printf(bio_err, "Failed reading from: %s\n", thisfile);
|
||||
continue;
|
||||
}
|
||||
BIO_free_all(in);
|
||||
if (bio_s_out != NULL)
|
||||
BIO_printf(bio_s_out, "Added ECH key pair from: %s\n", thisfile);
|
||||
loaded++;
|
||||
}
|
||||
if (SSL_CTX_set1_echstore(lctx, es) != 1) {
|
||||
BIO_printf(bio_err, "internal error\n");
|
||||
return 0;
|
||||
}
|
||||
if (bio_s_out != NULL)
|
||||
BIO_printf(bio_s_out, "Added %d ECH key pairs from: %s\n",
|
||||
loaded, thedir);
|
||||
*nloaded = loaded;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define IS_PROT_FLAG(o) \
|
||||
(o == OPT_TLS1 || o == OPT_TLS1_1 || o == OPT_TLS1_2 \
|
||||
|| o == OPT_TLS1_3 || o == OPT_DTLS || o == OPT_DTLS1 || o == OPT_DTLS1_2)
|
||||
@@ -1363,7 +1664,7 @@ int s_server_main(int argc, char *argv[])
|
||||
OPTION_CHOICE o;
|
||||
EVP_PKEY *s_key2 = NULL;
|
||||
X509 *s_cert2 = NULL;
|
||||
tlsextctx tlsextcbp = { NULL, NULL, SSL_TLSEXT_ERR_ALERT_WARNING };
|
||||
tlsextctx tlsextcbp = { NULL, NULL, SSL_TLSEXT_ERR_ALERT_WARNING, NULL };
|
||||
const char *ssl_config = NULL;
|
||||
int read_buf_len = 0;
|
||||
#ifndef OPENSSL_NO_NEXTPROTONEG
|
||||
@@ -1401,6 +1702,14 @@ int s_server_main(int argc, char *argv[])
|
||||
int max_early_data = -1, recv_max_early_data = -1;
|
||||
char *psksessf = NULL;
|
||||
int no_ca_names = 0;
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
char *echkeyfile = NULL;
|
||||
char *echkeydir = NULL;
|
||||
char *echnoretrydir = NULL;
|
||||
int ech_files_loaded = 0;
|
||||
int echtrialdecrypt = 0; /* trial decryption off by default */
|
||||
int echgrease_rc = 0; /* retry_config GREASEing off by default */
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_SCTP
|
||||
int sctp_label_bug = 0;
|
||||
#endif
|
||||
@@ -2000,6 +2309,23 @@ int s_server_main(int argc, char *argv[])
|
||||
case OPT_HTTP_SERVER_BINMODE:
|
||||
http_server_binmode = 1;
|
||||
break;
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
case OPT_ECH_PEM:
|
||||
echkeyfile = opt_arg();
|
||||
break;
|
||||
case OPT_ECH_DIR:
|
||||
echkeydir = opt_arg();
|
||||
break;
|
||||
case OPT_ECH_NORETRY:
|
||||
echnoretrydir = opt_arg();
|
||||
break;
|
||||
case OPT_ECH_TRIALDECRYPT:
|
||||
echtrialdecrypt = 1;
|
||||
break;
|
||||
case OPT_ECH_GREASE_RT:
|
||||
echgrease_rc = 1;
|
||||
break;
|
||||
#endif
|
||||
case OPT_NOCANAMES:
|
||||
no_ca_names = 1;
|
||||
break;
|
||||
@@ -2159,6 +2485,9 @@ int s_server_main(int argc, char *argv[])
|
||||
|
||||
if (s_cert2 == NULL)
|
||||
goto end;
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
tlsextcbp.scert = s_cert2;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if !defined(OPENSSL_NO_NEXTPROTONEG)
|
||||
@@ -2370,12 +2699,71 @@ int s_server_main(int argc, char *argv[])
|
||||
goto end;
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
if (echtrialdecrypt != 0)
|
||||
SSL_CTX_set_options(ctx, SSL_OP_ECH_TRIALDECRYPT);
|
||||
if (echgrease_rc != 0)
|
||||
SSL_CTX_set_options(ctx, SSL_OP_ECH_GREASE_RETRY_CONFIG);
|
||||
if (echkeyfile != NULL) {
|
||||
OSSL_ECHSTORE *es = NULL;
|
||||
BIO *in = NULL;
|
||||
|
||||
if ((in = BIO_new_file(echkeyfile, "r")) == NULL
|
||||
|| (es = OSSL_ECHSTORE_new(app_get0_libctx(),
|
||||
app_get0_propq()))
|
||||
== 0
|
||||
|| OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_FOR_RETRY) != 1
|
||||
|| SSL_CTX_set1_echstore(ctx, es) != 1) {
|
||||
BIO_printf(bio_err, "Failed reading: %s\n", echkeyfile);
|
||||
OSSL_ECHSTORE_free(es);
|
||||
BIO_free_all(in);
|
||||
goto end;
|
||||
}
|
||||
OSSL_ECHSTORE_free(es);
|
||||
BIO_free_all(in);
|
||||
if (bio_s_out != NULL)
|
||||
BIO_printf(bio_s_out, "Added ECH key pair from: %s\n", echkeyfile);
|
||||
ech_files_loaded++;
|
||||
}
|
||||
if (echkeydir != NULL) {
|
||||
int nloaded = 0;
|
||||
|
||||
if (ech_load_dir(ctx, echkeydir, OSSL_ECH_FOR_RETRY, &nloaded) != 1) {
|
||||
BIO_printf(bio_err, "error loading from %s\n", echkeydir);
|
||||
goto end;
|
||||
}
|
||||
ech_files_loaded += nloaded;
|
||||
}
|
||||
if (echnoretrydir != NULL) {
|
||||
int nloaded = 0;
|
||||
|
||||
if (ech_load_dir(ctx, echnoretrydir, OSSL_ECH_NO_RETRY,
|
||||
&nloaded)
|
||||
!= 1) {
|
||||
BIO_printf(bio_err, "error loading from %s\n", echnoretrydir);
|
||||
goto end;
|
||||
}
|
||||
ech_files_loaded += nloaded;
|
||||
}
|
||||
if ((echkeyfile != NULL || echkeydir != NULL || echnoretrydir != NULL)
|
||||
&& bio_s_out != NULL) {
|
||||
BIO_printf(bio_s_out, "Loaded %d ECH key pairs in total\n",
|
||||
ech_files_loaded);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (s_cert2) {
|
||||
ctx2 = SSL_CTX_new_ex(app_get0_libctx(), app_get0_propq(), meth);
|
||||
if (ctx2 == NULL) {
|
||||
ERR_print_errors(bio_err);
|
||||
goto end;
|
||||
}
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
if (echtrialdecrypt != 0)
|
||||
SSL_CTX_set_options(ctx2, SSL_OP_ECH_TRIALDECRYPT);
|
||||
if (echgrease_rc != 0)
|
||||
SSL_CTX_set_options(ctx, SSL_OP_ECH_GREASE_RETRY_CONFIG);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ctx2 != NULL) {
|
||||
@@ -2434,6 +2822,13 @@ int s_server_main(int argc, char *argv[])
|
||||
if (alpn_ctx.data)
|
||||
SSL_CTX_set_alpn_select_cb(ctx, alpn_cb, &alpn_ctx);
|
||||
|
||||
/*
|
||||
* If we have a 2nd context to which we might switch, then set
|
||||
* the same alpn callback for that too.
|
||||
*/
|
||||
if (s_cert2 != NULL && alpn_ctx.data != NULL)
|
||||
SSL_CTX_set_alpn_select_cb(ctx2, alpn_cb, &alpn_ctx);
|
||||
|
||||
if (!no_dhe) {
|
||||
EVP_PKEY *dhpkey = NULL;
|
||||
|
||||
@@ -2508,9 +2903,21 @@ int s_server_main(int argc, char *argv[])
|
||||
goto end;
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
/*
|
||||
* Giving the same chain to the 2nd key pair works for our tests.
|
||||
* It would be better to supply s_chain_file2 as a new CLA in case
|
||||
* the paths are very different but as that's not needed for tests,
|
||||
* I didn't do it.
|
||||
*/
|
||||
if (ctx2 != NULL
|
||||
&& !set_cert_key_stuff(ctx2, s_cert2, s_key2, s_chain, build_chain))
|
||||
goto end;
|
||||
#else
|
||||
if (ctx2 != NULL
|
||||
&& !set_cert_key_stuff(ctx2, s_cert2, s_key2, NULL, build_chain))
|
||||
goto end;
|
||||
#endif
|
||||
|
||||
if (s_dcert != NULL) {
|
||||
if (!set_cert_key_stuff(ctx, s_dcert, s_dkey, s_dchain, build_chain))
|
||||
@@ -2591,10 +2998,19 @@ int s_server_main(int argc, char *argv[])
|
||||
goto end;
|
||||
}
|
||||
tlsextcbp.biodebug = bio_s_out;
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
SSL_CTX_set_tlsext_servername_callback(ctx2, ssl_ech_servername_cb);
|
||||
SSL_CTX_set_tlsext_servername_arg(ctx2, &tlsextcbp);
|
||||
SSL_CTX_set_tlsext_servername_callback(ctx, ssl_ech_servername_cb);
|
||||
SSL_CTX_set_tlsext_servername_arg(ctx, &tlsextcbp);
|
||||
SSL_CTX_ech_set_callback(ctx2, ech_print_cb);
|
||||
SSL_CTX_ech_set_callback(ctx, ech_print_cb);
|
||||
#else
|
||||
SSL_CTX_set_tlsext_servername_callback(ctx2, ssl_servername_cb);
|
||||
SSL_CTX_set_tlsext_servername_arg(ctx2, &tlsextcbp);
|
||||
SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
|
||||
SSL_CTX_set_tlsext_servername_arg(ctx, &tlsextcbp);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef OPENSSL_NO_SRP
|
||||
@@ -2622,6 +3038,11 @@ int s_server_main(int argc, char *argv[])
|
||||
#endif
|
||||
if (set_keylog_file(ctx, keylog_file))
|
||||
goto end;
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
/* not really an ECH issue but needed */
|
||||
if (ctx2 != NULL && set_keylog_file(ctx2, keylog_file))
|
||||
goto end;
|
||||
#endif
|
||||
|
||||
if (max_early_data >= 0)
|
||||
SSL_CTX_set_max_early_data(ctx, max_early_data);
|
||||
@@ -3637,6 +4058,10 @@ static int www_body(int s, int stype, int prot, unsigned char *context)
|
||||
X509 *peer = NULL;
|
||||
STACK_OF(SSL_CIPHER) *sk;
|
||||
static const char *space = " ";
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
char *ech_inner = NULL, *ech_outer = NULL;
|
||||
int echrv = 0;
|
||||
#endif
|
||||
|
||||
if (www == 1 && HAS_PREFIX(buf, "GET /reneg")) {
|
||||
if (HAS_PREFIX(buf, "GET /renegcert"))
|
||||
@@ -3700,6 +4125,80 @@ static int www_body(int s, int stype, int prot, unsigned char *context)
|
||||
}
|
||||
BIO_puts(io, "\n");
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
/* Customise output a bit to show ECH info at top */
|
||||
BIO_puts(io, "<h1>OpenSSL with ECH</h1>\n");
|
||||
BIO_puts(io, "<h2>\n");
|
||||
echrv = SSL_ech_get1_status(con, &ech_inner, &ech_outer);
|
||||
switch (echrv) {
|
||||
case SSL_ECH_STATUS_NOT_TRIED:
|
||||
BIO_puts(io, "ECH not attempted\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_FAILED:
|
||||
BIO_puts(io, "ECH tried but failed\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_FAILED_ECH:
|
||||
BIO_puts(io, "ECH tried but we got ECH which is weird\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_BAD_NAME:
|
||||
BIO_puts(io, "ECH worked but bad name\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_BACKEND:
|
||||
BIO_printf(io, "ECH acting as backend\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_NOT_CONFIGURED:
|
||||
BIO_printf(io, "ECH not configured\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_GREASE:
|
||||
BIO_printf(io, "ECH attempt we interpret as GREASE\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_GREASE_ECH:
|
||||
BIO_printf(io, "ECH attempt we interpret as GREASE, + ECH\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_BAD_CALL:
|
||||
BIO_printf(io, "ECH bad input to API\n");
|
||||
break;
|
||||
case SSL_ECH_STATUS_SUCCESS:
|
||||
BIO_printf(io, "ECH success: outer sni: %s, inner sni: %s\n",
|
||||
(ech_outer == NULL ? "none" : ech_outer),
|
||||
(ech_inner == NULL ? "none" : ech_inner));
|
||||
break;
|
||||
default:
|
||||
BIO_printf(io, " Error getting ECH status\n");
|
||||
break;
|
||||
}
|
||||
BIO_puts(io, "</h2>\n");
|
||||
BIO_puts(io, "<h2>TLS Session details</h2>\n");
|
||||
BIO_puts(io, "<pre>\n");
|
||||
/*
|
||||
* also dump session info to server stdout for debugging
|
||||
*/
|
||||
SSL_SESSION_print(bio_s_out, SSL_get_session(con));
|
||||
BIO_puts(io, "<pre>\n");
|
||||
BIO_puts(io, "\n");
|
||||
for (i = 0; i < local_argc; i++) {
|
||||
const char *myp;
|
||||
|
||||
for (myp = local_argv[i]; *myp; myp++)
|
||||
switch (*myp) {
|
||||
case '<':
|
||||
BIO_puts(io, "<");
|
||||
break;
|
||||
case '>':
|
||||
BIO_puts(io, ">");
|
||||
break;
|
||||
case '&':
|
||||
BIO_puts(io, "&");
|
||||
break;
|
||||
default:
|
||||
BIO_write(io, myp, 1);
|
||||
break;
|
||||
}
|
||||
BIO_write(io, " ", 1);
|
||||
}
|
||||
BIO_puts(io, "\n");
|
||||
#endif
|
||||
|
||||
ssl_print_secure_renegotiation_notes(io, con);
|
||||
|
||||
/*
|
||||
|
||||
@@ -124,6 +124,14 @@ B<openssl> B<s_client>
|
||||
[B<-enable_client_rpk>]
|
||||
[I<host>:I<port>]
|
||||
[B<-ech_config_list>]
|
||||
[B<-ech_outer_alpn> I<protocols>]
|
||||
[B<-ech_grease>]
|
||||
[B<-ech_grease_suite> I<suite>]
|
||||
[B<-ech_grease_type> I<type>]
|
||||
[B<-ech_ignore_cid>]
|
||||
[B<-ech_outer_sni> I<value>]
|
||||
[B<-ech_no_outer_sni>]
|
||||
[B<-ech_select> I<config-index>]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@@ -818,6 +826,63 @@ nor B<-connect> are provided, falls back to attempting to connect to
|
||||
I<localhost> on port I<4433>.
|
||||
If the host string is an IPv6 address, it must be enclosed in C<[> and C<]>.
|
||||
|
||||
=item B<-ech_outer_alpn> I<protocols>
|
||||
|
||||
When doing Encrypted Client Hello (ECH), this allows the caller to specify
|
||||
ALPN values to use in the outer ClientHello. (A "normal" ALPN value
|
||||
specified via -alpn will be used in the inner ClientHello.)
|
||||
|
||||
=item B<-ech_grease>
|
||||
|
||||
When not really doing Encrypted Client Hello (ECH), one can emit a so-called
|
||||
GREASE value, which is essentially a random value in order to try ensure that
|
||||
server code is less likely to ossify.
|
||||
|
||||
=item B<-ech_grease_suite> I<suite>
|
||||
|
||||
When B<-ech_grease> is specified, one can choose which ECH ciphersuite to use
|
||||
via this parameter.
|
||||
|
||||
The comma-separated suite string names an HPKE suite in the form of
|
||||
I<kem>,I<kdf>,I<aead>, e.g. "x25519,hkdf-sha256,aes256gcm" or can use
|
||||
the numeric values (in decimal or hexadecimal form) from the HPKE specification
|
||||
so "0x20,0x01,0x02" is the same as the previous example.
|
||||
|
||||
KEM values supported: p256 or 0x10; p384 or 0x11, p521 or 0x12, x25519 or 0x20, x448 or 0x21
|
||||
|
||||
KDF values supported: hkdf-sha256 or 0x01, hkdf-sha384 or 0x02, hkdf-sha512 or 0x03
|
||||
|
||||
AEAD values supported: aes128gcm or 0x01, aes256gcm or 0x02, chachapoly1305 or 0x03
|
||||
|
||||
=item B<-ech_grease_type> I<type>
|
||||
|
||||
Allows the client to set the TLS extension type for a GREASEd ECH value
|
||||
(currently equivalent to the ECH version number). The current default is
|
||||
0xfe0d.
|
||||
|
||||
=item B<-ech_ignore_cid>
|
||||
|
||||
Encrypted Client Hello (ECH) extensions contain a configuration identifier
|
||||
(cid) taken from the ECHConfigList usually found in the domain name system
|
||||
(DNS). As those identifiers could be revealing, the client has the option to
|
||||
use a random value instead.
|
||||
|
||||
=item B<-ech_outer_sni> I<value>
|
||||
|
||||
When doing Encrypted Client Hello (ECH), this allows the caller to specify a
|
||||
subject name indication (SNI) value to use in the outer ClientHello over-riding
|
||||
the public_name value from the relevant ECHConfigList.
|
||||
|
||||
=item B<-ech_no_outer_sni>
|
||||
|
||||
Setting this flag means no SNI will be emitted in the outer ClientHello.
|
||||
|
||||
=item B<-ech_select> I<config-index>
|
||||
|
||||
If an ECHConfigList contains more than one ECHConfig then the client will by
|
||||
default use the first that works. This allows the caller to specify which
|
||||
ECHConfig to use (using a zero-based index).
|
||||
|
||||
=back
|
||||
|
||||
=head1 CONNECTED COMMANDS (BASIC)
|
||||
@@ -1044,6 +1109,8 @@ The
|
||||
and B<-ocsp_check_all>
|
||||
options were added in OpenSSL 3.6.
|
||||
|
||||
The B<ech> options were added in OpenSSL 4.0.
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2000-2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
|
||||
@@ -135,6 +135,11 @@ B<openssl> B<s_server>
|
||||
{- $OpenSSL::safe::opt_provider_synopsis -}
|
||||
[B<-enable_server_rpk>]
|
||||
[B<-enable_client_rpk>]
|
||||
[B<-ech_key> I<filename>]
|
||||
[B<-ech_dir> I<dirname>]
|
||||
[B<-ech_noretry_dir> I<dirname>]
|
||||
[B<-ech_trialdecrypt>]
|
||||
[B<-ech_greaseretries>]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@@ -817,6 +822,32 @@ certificates can still elect to send X.509 certificates as usual.
|
||||
|
||||
Raw public keys are extracted from the configured certificate/private key.
|
||||
|
||||
=item B<-ech_key> I<filename>
|
||||
|
||||
Load one Encrypted Client Hello (ECH) key pair.
|
||||
|
||||
=item B<-ech_dir> I<dirname>
|
||||
|
||||
Attempt to load an ECH key pair from every file in the named directory.
|
||||
Any keys successfully loaded will be returned in 'retry_configs'.
|
||||
|
||||
=item B<-ech_noretry_dir> I<dirname>
|
||||
|
||||
Attempt to load an ECH key pair from every file in the named directory.
|
||||
Keys loaded will not be returned in 'retry_configs'.
|
||||
|
||||
=item B<-ech_trialdecrypt>
|
||||
|
||||
When an Encrypted Client Hello (ECH) extension is seen in a ClientHello,
|
||||
attempt to decrypt with all known ECH private keys if necessary. Without
|
||||
this, the ECH "config_id" is used to match against the loaded ECH private
|
||||
keys and decryption is only attempted when there's a match.
|
||||
|
||||
=item B<-ech_greaseretries>
|
||||
|
||||
If set, servers will add GREASEy ECHConfig values to those sent
|
||||
in retry_configs.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CONNECTED COMMANDS
|
||||
@@ -931,6 +962,8 @@ options were added in OpenSSL 3.2.
|
||||
|
||||
The B<-status_all> option was added in OpenSSL 3.6.
|
||||
|
||||
The B<ech> options were added in OpenSSL 4.0.
|
||||
|
||||
The B<engine> option was removed in OpenSSL 4.0.
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
@@ -392,7 +392,7 @@ int ossl_ech_pick_matching_cfg(SSL_CONNECTION *s, OSSL_ECHSTORE_ENTRY **ee,
|
||||
num = sk_OSSL_ECHSTORE_ENTRY_num(es->entries);
|
||||
/* allow API-set pref to override */
|
||||
hn = s->ext.ech.outer_hostname;
|
||||
hnlen = (hn == NULL ? 0 : strlen(hn));
|
||||
hnlen = (hn == NULL ? 0 : (unsigned int)strlen(hn));
|
||||
if (hnlen != 0)
|
||||
nameoverride = 1;
|
||||
if (s->ext.ech.no_outer == 1) {
|
||||
@@ -518,7 +518,7 @@ int ossl_ech_encode_inner(SSL_CONNECTION *s, unsigned char **encoded,
|
||||
}
|
||||
/* now copy the rest, as "proper" exts, into encoded inner */
|
||||
for (ind = 0; ind < TLSEXT_IDX_num_builtins; ind++) {
|
||||
if (raws[ind].present == 0 || ossl_ech_2bcompressed(ind) == 1)
|
||||
if (raws[ind].present == 0 || ossl_ech_2bcompressed((int)ind) == 1)
|
||||
continue;
|
||||
if (!WPACKET_put_bytes_u16(&inner, raws[ind].type)
|
||||
|| !WPACKET_sub_memcpy_u16(&inner, PACKET_data(&raws[ind].data),
|
||||
@@ -637,15 +637,16 @@ size_t ossl_ech_calc_padding(SSL_CONNECTION *s, OSSL_ECHSTORE_ENTRY *ee,
|
||||
/* do weirder padding if SNI present in inner */
|
||||
if (s->ext.hostname != NULL) {
|
||||
isnilen = strlen(s->ext.hostname) + 9;
|
||||
innersnipadding = (mnl > isnilen) ? mnl - isnilen : 0;
|
||||
innersnipadding = (mnl > isnilen) ? (int)(mnl - isnilen) : 0;
|
||||
} else {
|
||||
innersnipadding = mnl + 9;
|
||||
innersnipadding = (int)mnl + 9;
|
||||
}
|
||||
}
|
||||
/* padding is after the inner client hello has been encoded */
|
||||
length_with_snipadding = innersnipadding + encoded_len;
|
||||
length_with_snipadding = innersnipadding + (int)encoded_len;
|
||||
length_of_padding = 31 - ((length_with_snipadding - 1) % 32);
|
||||
length_with_padding = encoded_len + length_of_padding + innersnipadding;
|
||||
length_with_padding = (int)encoded_len + length_of_padding
|
||||
+ innersnipadding;
|
||||
/*
|
||||
* Finally - make sure final result is longer than padding target
|
||||
* and a multiple of our padding increment.
|
||||
|
||||
@@ -260,6 +260,7 @@ int SSL_ech_set1_grease_suite(SSL *ssl, const char *suite)
|
||||
if (s->ext.ech.grease_suite == NULL)
|
||||
return 0;
|
||||
s->ext.ech.attempted = 1;
|
||||
s->ext.ech.grease = OSSL_ECH_IS_GREASE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -272,6 +273,7 @@ int SSL_ech_set_grease_type(SSL *ssl, uint16_t type)
|
||||
return 0;
|
||||
s->ext.ech.attempted_type = type;
|
||||
s->ext.ech.attempted = 1;
|
||||
s->ext.ech.grease = OSSL_ECH_IS_GREASE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -269,7 +269,7 @@ static int ech_final_config_checks(OSSL_ECHSTORE_ENTRY *ee)
|
||||
/* check no mandatory exts (with high bit set in type) */
|
||||
num = (ee->exts == NULL ? 0 : sk_OSSL_ECHEXT_num(ee->exts));
|
||||
for (ind = 0; ind != num; ind++) {
|
||||
OSSL_ECHEXT *oe = sk_OSSL_ECHEXT_value(ee->exts, ind);
|
||||
OSSL_ECHEXT *oe = sk_OSSL_ECHEXT_value(ee->exts, (int)ind);
|
||||
|
||||
if (oe->type & 0x8000) {
|
||||
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
|
||||
@@ -329,7 +329,7 @@ static int ech_decode_one_entry(OSSL_ECHSTORE_ENTRY **rent, PACKET *pkt,
|
||||
ERR_raise(ERR_LIB_SSL, SSL_R_ECH_DECODE_ERROR);
|
||||
goto err;
|
||||
}
|
||||
ech_content_length = PACKET_remaining(&ver_pkt);
|
||||
ech_content_length = (unsigned int)PACKET_remaining(&ver_pkt);
|
||||
switch (ee->version) {
|
||||
case OSSL_ECH_RFCXXXX_VERSION:
|
||||
break;
|
||||
|
||||
@@ -1088,7 +1088,7 @@ int tls_construct_extensions(SSL_CONNECTION *s, WPACKET *pkt,
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
/* do compressed in pass 0, non-compressed in pass 1 */
|
||||
if (ossl_ech_2bcompressed(i) == pass)
|
||||
if (ossl_ech_2bcompressed((int)i) == pass)
|
||||
continue;
|
||||
/* stash index - needed for COMPRESS ECH handling */
|
||||
s->ext.ech.ext_ind = (int)i;
|
||||
|
||||
@@ -2576,14 +2576,14 @@ EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt,
|
||||
size_t cipherlen = 0, aad_len = 0, lenclen = 0, mypub_len = 0;
|
||||
size_t info_len = OSSL_ECH_MAX_INFO_LEN, clear_len = 0, encoded_len = 0;
|
||||
/* whether or not we've been asked to GREASE, one way or another */
|
||||
int grease_opt_set = (s->ext.ech.grease == OSSL_ECH_IS_GREASE
|
||||
int grease_opt_set = ((s->ext.ech.grease == OSSL_ECH_IS_GREASE)
|
||||
|| ((s->options & SSL_OP_ECH_GREASE) != 0));
|
||||
|
||||
/* if we're not doing real ECH and not GREASEing then exit */
|
||||
if (s->ext.ech.attempted_type != TLSEXT_TYPE_ech && grease_opt_set == 0)
|
||||
return EXT_RETURN_NOT_SENT;
|
||||
/* send grease if not really attempting ECH */
|
||||
if (s->ext.ech.attempted == 0 && grease_opt_set == 1) {
|
||||
if (grease_opt_set == 1) {
|
||||
if (s->hello_retry_request == SSL_HRR_PENDING
|
||||
&& s->ext.ech.sent != NULL) {
|
||||
/* re-tx already sent GREASEy ECH */
|
||||
@@ -2786,7 +2786,7 @@ int tls_parse_stoc_ech(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
|
||||
return 0;
|
||||
}
|
||||
rval = PACKET_data(&rcfgs_pkt);
|
||||
rlen = PACKET_remaining(&rcfgs_pkt);
|
||||
rlen = (unsigned int)PACKET_remaining(&rcfgs_pkt);
|
||||
OPENSSL_free(s->ext.ech.returned);
|
||||
s->ext.ech.returned = NULL;
|
||||
srval = OPENSSL_malloc(rlen + 2);
|
||||
|
||||
+66
-5
@@ -11,6 +11,7 @@
|
||||
#include <openssl/hpke.h>
|
||||
#include "testutil.h"
|
||||
#include "helpers/ssltestlib.h"
|
||||
#include "internal/packet.h"
|
||||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
|
||||
@@ -23,15 +24,58 @@ static char *certsdir = NULL;
|
||||
static char *cert = NULL;
|
||||
static char *privkey = NULL;
|
||||
static char *rootcert = NULL;
|
||||
static int ch_test_cb_ok = 0;
|
||||
|
||||
/* TODO(ECH): add some testing of SSL_OP_ECH_IGNORE_CID */
|
||||
|
||||
/* callback */
|
||||
static unsigned int test_cb(SSL *s, const char *str)
|
||||
/* ECH callback */
|
||||
static unsigned int ech_test_cb(SSL *s, const char *str)
|
||||
{
|
||||
if (verbose)
|
||||
TEST_info("ech_test_cb called");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ClientHello callback */
|
||||
static int ch_test_cb(SSL *ssl, int *al, void *arg)
|
||||
{
|
||||
char *servername = NULL;
|
||||
const unsigned char *pos;
|
||||
size_t remaining;
|
||||
unsigned int servname_type;
|
||||
PACKET pkt, sni, hostname;
|
||||
|
||||
if (verbose) {
|
||||
TEST_info("ch_test_cb called");
|
||||
if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_ech, &pos, &remaining)) {
|
||||
TEST_info("there is an ECH extension");
|
||||
} else {
|
||||
TEST_info("there is NO ECH extension");
|
||||
}
|
||||
}
|
||||
if (!SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &pos,
|
||||
&remaining)
|
||||
|| remaining <= 2)
|
||||
goto give_up;
|
||||
if (!PACKET_buf_init(&pkt, pos, remaining)
|
||||
|| !PACKET_as_length_prefixed_2(&pkt, &sni)
|
||||
|| !PACKET_get_1(&sni, &servname_type)
|
||||
|| servname_type != TLSEXT_NAMETYPE_host_name
|
||||
|| !PACKET_as_length_prefixed_2(&sni, &hostname)
|
||||
|| (PACKET_remaining(&hostname) > TLSEXT_MAXLEN_host_name)
|
||||
|| PACKET_contains_zero_byte(&hostname)
|
||||
|| !PACKET_strndup(&hostname, &servername))
|
||||
goto give_up;
|
||||
if (verbose)
|
||||
TEST_info("servername: %s", servername);
|
||||
OPENSSL_free(servername);
|
||||
/* signal to caller all is good */
|
||||
ch_test_cb_ok = 1;
|
||||
return 1;
|
||||
give_up:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The define/vars below and the 3 callback functions are modified
|
||||
* from test/sslapitest.c
|
||||
@@ -1097,8 +1141,8 @@ static int ech_api_basic_calls(void)
|
||||
|| !TEST_false(rclen)
|
||||
|| !TEST_ptr_eq(rc, NULL))
|
||||
goto end;
|
||||
SSL_CTX_ech_set_callback(ctx, test_cb);
|
||||
SSL_ech_set_callback(s, test_cb);
|
||||
SSL_CTX_ech_set_callback(ctx, ech_test_cb);
|
||||
SSL_ech_set_callback(s, ech_test_cb);
|
||||
|
||||
/* all good */
|
||||
rv = 1;
|
||||
@@ -1147,6 +1191,7 @@ end:
|
||||
#define OSSL_ECH_TEST_EARLY 2
|
||||
#define OSSL_ECH_TEST_CUSTOM 3
|
||||
#define OSSL_ECH_TEST_ENOE 4 /* early + no-ech */
|
||||
#define OSSL_ECH_TEST_CBS 5 /* test callbacks */
|
||||
/* note: early-data is prohibited after HRR so no tests for that */
|
||||
|
||||
/*
|
||||
@@ -1226,6 +1271,10 @@ static int test_ech_roundtrip_helper(int idx, int combo)
|
||||
&server, NULL, &server)))
|
||||
goto end;
|
||||
}
|
||||
if (combo == OSSL_ECH_TEST_CBS) {
|
||||
SSL_CTX_ech_set_callback(sctx, ech_test_cb);
|
||||
SSL_CTX_set_client_hello_cb(sctx, ch_test_cb, NULL);
|
||||
}
|
||||
if (combo != OSSL_ECH_TEST_ENOE
|
||||
&& !TEST_true(SSL_CTX_set1_echstore(cctx, es)))
|
||||
goto end;
|
||||
@@ -1261,9 +1310,11 @@ static int test_ech_roundtrip_helper(int idx, int combo)
|
||||
if (combo == OSSL_ECH_TEST_ENOE
|
||||
&& !TEST_int_eq(clientstatus, SSL_ECH_STATUS_NOT_CONFIGURED))
|
||||
goto end;
|
||||
if (combo == OSSL_ECH_TEST_CBS && !TEST_int_eq(ch_test_cb_ok, 1))
|
||||
goto end;
|
||||
/* all good */
|
||||
if (combo == OSSL_ECH_TEST_BASIC || combo == OSSL_ECH_TEST_HRR
|
||||
|| combo == OSSL_ECH_TEST_CUSTOM) {
|
||||
|| combo == OSSL_ECH_TEST_CUSTOM || combo == OSSL_ECH_TEST_CBS) {
|
||||
res = 1;
|
||||
goto end;
|
||||
}
|
||||
@@ -1336,6 +1387,7 @@ end:
|
||||
SSL_free(serverssl);
|
||||
SSL_CTX_free(cctx);
|
||||
SSL_CTX_free(sctx);
|
||||
ch_test_cb_ok = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -1379,6 +1431,14 @@ static int ech_enoe_test(int idx)
|
||||
return test_ech_roundtrip_helper(idx, OSSL_ECH_TEST_ENOE);
|
||||
}
|
||||
|
||||
/* Test a roundtrip with ECH, and callbacks */
|
||||
static int ech_cb_test(int idx)
|
||||
{
|
||||
if (verbose)
|
||||
TEST_info("Doing: ech + callbacks test ");
|
||||
return test_ech_roundtrip_helper(idx, OSSL_ECH_TEST_CBS);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int setup_tests(void)
|
||||
@@ -1422,6 +1482,7 @@ int setup_tests(void)
|
||||
ADD_ALL_TESTS(test_ech_early, suite_combos);
|
||||
ADD_ALL_TESTS(ech_custom_test, suite_combos);
|
||||
ADD_ALL_TESTS(ech_enoe_test, suite_combos);
|
||||
ADD_ALL_TESTS(ech_cb_test, suite_combos);
|
||||
/* TODO(ECH): add more test code as other PRs done */
|
||||
return 1;
|
||||
err:
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
#! /usr/bin/env perl
|
||||
# Copyright 2023-2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (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
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use IPC::Open3;
|
||||
use OpenSSL::Test qw/:DEFAULT srctop_file bldtop_file/;
|
||||
use OpenSSL::Test::Utils;
|
||||
use Symbol 'gensym';
|
||||
|
||||
# servers randomly pick a port, then set this for clients to use
|
||||
# we also record the pid so we can kill it later if needed
|
||||
my $s_server_port = 0;
|
||||
my $s_server_pid = 0;
|
||||
my $s_client_match = 0;
|
||||
|
||||
my $test_name = "test_ech_client_server";
|
||||
setup($test_name);
|
||||
|
||||
plan skip_all => "$test_name requires EC cryptography"
|
||||
if disabled("ec") || disabled("ecx");
|
||||
plan skip_all => "$test_name requires sock enabled"
|
||||
if disabled("sock");
|
||||
plan skip_all => "$test_name requires TLSv1.3 enabled"
|
||||
if disabled("tls1_3");
|
||||
plan skip_all => "$test_name is not available Windows or VMS"
|
||||
if $^O =~ /^(VMS|MSWin32|msys)$/;
|
||||
|
||||
plan tests => 18;
|
||||
|
||||
my $shlib_wrap = bldtop_file("util", "shlib_wrap.sh");
|
||||
my $apps_openssl = bldtop_file("apps", "openssl");
|
||||
|
||||
my $echconfig_pem = srctop_file("test", "certs", "ech-eg.pem");
|
||||
my $badconfig_pem = srctop_file("test", "certs", "ech-mid.pem");
|
||||
my $server_pem = srctop_file("test", "certs", "echserver.pem");
|
||||
my $server_key = srctop_file("test", "certs", "echserver.key");
|
||||
my $root_pem = srctop_file("test", "certs", "rootcert.pem");
|
||||
|
||||
sub extract_ecl()
|
||||
{
|
||||
# extract b64 encoded ECHConfigList from pem file
|
||||
my $lb64 = "";
|
||||
my $inwanted = 0;
|
||||
open( my $fh, '<', $echconfig_pem ) or die "Can't open $echconfig_pem $!";
|
||||
while( my $line = <$fh>) {
|
||||
chomp $line;
|
||||
if ( $line =~ /^-----BEGIN ECHCONFIG/) {
|
||||
$inwanted = 1;
|
||||
} elsif ( $line =~ /^-----END ECHCONFIG/) {
|
||||
$inwanted = 0;
|
||||
} elsif ($inwanted == 1) {
|
||||
$lb64 .= $line;
|
||||
}
|
||||
}
|
||||
print("base64 ECHConfigList: $lb64\n");
|
||||
return($lb64);
|
||||
}
|
||||
|
||||
my $good_b64 = extract_ecl();
|
||||
|
||||
sub start_ech_client_server
|
||||
{
|
||||
my ( $test_type, $winpattern ) = @_;
|
||||
|
||||
# start an s_server listening on some random port, with ECH enabled
|
||||
# and willing to accept one request
|
||||
|
||||
# openssl s_server -accept 0 -naccept 1
|
||||
# -key $server_key -cert $server_cert
|
||||
# -key2 $server_key -cert2 $server_cert
|
||||
# -ech_key $echconfig_pem
|
||||
# -servername example.com
|
||||
# -tls1_3
|
||||
my @s_server_cmd;
|
||||
if ($test_type eq "cid-free" ) {
|
||||
# turn on trial-decrypt, so client can use random CID
|
||||
@s_server_cmd = ("s_server", "-accept", "0", "-naccept", "1",
|
||||
"-cert", $server_pem, "-key", $server_key,
|
||||
"-cert2", $server_pem, "-key2", $server_key,
|
||||
"-ech_key", $echconfig_pem,
|
||||
"-servername", "example.com",
|
||||
"-ech_trialdecrypt",
|
||||
"-tls1_3");
|
||||
} else {
|
||||
# default for all other tests (for now)
|
||||
@s_server_cmd = ("s_server", "-accept", "0", "-naccept", "1",
|
||||
"-cert", $server_pem, "-key", $server_key,
|
||||
"-cert2", $server_pem, "-key2", $server_key,
|
||||
"-ech_key", $echconfig_pem,
|
||||
"-servername", "example.com",
|
||||
"-ech_greaseretries",
|
||||
"-tls1_3");
|
||||
}
|
||||
print("@s_server_cmd\n");
|
||||
$s_server_pid = open3(my $s_server_i, my $s_server_o,
|
||||
my $s_server_e = gensym,
|
||||
$shlib_wrap, $apps_openssl, @s_server_cmd);
|
||||
# we're looking for...
|
||||
# ACCEPT 0.0.0.0:45921
|
||||
# ACCEPT [::]:45921
|
||||
$s_server_port = "0";
|
||||
while (<$s_server_o>) {
|
||||
print($_);
|
||||
chomp;
|
||||
if (/^ACCEPT 0.0.0.0:(\d+)/) {
|
||||
$s_server_port = $1;
|
||||
last;
|
||||
} elsif (/^ACCEPT \[::\]:(\d+)/) {
|
||||
$s_server_port = $1;
|
||||
last;
|
||||
} elsif (/^Using default/) {
|
||||
;
|
||||
} elsif (/^Added ECH key pair/) {
|
||||
;
|
||||
} elsif (/^Loaded/) {
|
||||
;
|
||||
} elsif (/^Setting secondary/) {
|
||||
;
|
||||
} else {
|
||||
last;
|
||||
}
|
||||
}
|
||||
# openssl s_client -connect localhost:NNNNN
|
||||
# -servername server.example
|
||||
# -CAfile test/certs/rootcert.pem
|
||||
# -ech_config_list "ADn+...AA="
|
||||
# -prexit
|
||||
my @s_client_cmd;
|
||||
if ($test_type eq "GREASE-suite" ) {
|
||||
# GREASE
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_grease_suite", "0x21,2,3",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "lots-of-options" ) {
|
||||
# real ECH with lots of options
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_config_list", $good_b64,
|
||||
"-ech_outer_sni", "foodle.doodle",
|
||||
"-ech_select", "0",
|
||||
"-alpn", "http/1.1",
|
||||
"-ech_outer_alpn", "http451",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "GREASE-type" ) {
|
||||
# GREASE with suite
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_grease_type", "12345",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "GREASE" ) {
|
||||
# GREASE with suite
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_grease",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "no-outer" ) {
|
||||
# Real ECH, no outer SNI
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_config_list", $good_b64,
|
||||
"-ech_no_outer_sni",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "bad-ech" ) {
|
||||
# bad ECH
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_config_list", "AEH+DQA91wAgACCBdNrnZxqNrUXSyimqqnfmNG4lHtVsbmaaIeRoUoFWFQAEAAEAAQAOc2VydmVyLmV4YW1wbGUAAA==",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "cid-free" ) {
|
||||
# Real ECH, ignore CID
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_config_list", $good_b64,
|
||||
"-ech_ignore_cid",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "cid-wrong" ) {
|
||||
# Real ECH, ignore CID, no trial decrypt
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_config_list", $good_b64,
|
||||
"-ech_ignore_cid",
|
||||
"-prexit");
|
||||
} else {
|
||||
# Real ECH, and default
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_config_list", $good_b64,
|
||||
"-prexit");
|
||||
}
|
||||
print("@s_client_cmd\n");
|
||||
local (*sc_input);
|
||||
my $s_client_pid = open3(*sc_input, my $s_client_o,
|
||||
my $s_client_e = gensym,
|
||||
$shlib_wrap, $apps_openssl, @s_client_cmd);
|
||||
print sc_input "Q\n";
|
||||
close(sc_input);
|
||||
waitpid($s_client_pid, 0);
|
||||
# the output from s_client that we want to check is written to its
|
||||
# stdout, e.g: "^ECH: success, yay!"
|
||||
$s_client_match = 0;
|
||||
while (<$s_client_o>) {
|
||||
print($_);
|
||||
chomp;
|
||||
if (/$winpattern/) {
|
||||
$s_client_match = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
my $stillthere = kill 0, $s_server_pid;
|
||||
if ($stillthere) {
|
||||
print("s_server process ($s_server_pid) is not dead yet.\n");
|
||||
kill 'HUP', $s_server_pid;
|
||||
}
|
||||
}
|
||||
|
||||
sub basic_test {
|
||||
print("\n\nBasic test.\n");
|
||||
my $tt = "basic";
|
||||
my $win = "^ECH: success";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client with ECH on command line");
|
||||
}
|
||||
|
||||
sub wrong_test {
|
||||
print("\n\nWrong ECHConfig test.\n");
|
||||
# hardcoded 'cause we want a fail
|
||||
my $tt="bad-ech",
|
||||
my $win="^ECH: failed.retry-configs: -105";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client with bad ECH");
|
||||
}
|
||||
|
||||
sub grease_test {
|
||||
print("\n\nGREASE ECHConfig test.\n");
|
||||
my $tt="GREASE";
|
||||
my $win="^ECH: GREASE";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client with GREASE ECH");
|
||||
}
|
||||
|
||||
sub grease_suite_test {
|
||||
print("\n\nGREASE suite ECHConfig test.\n");
|
||||
my $tt="GREASE-suite";
|
||||
my $win="^ECH: GREASE";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client with GREASE-suite ECH");
|
||||
}
|
||||
|
||||
sub grease_type_test {
|
||||
print("\n\nGREASE type ECH test.\n");
|
||||
my $tt="GREASE-type";
|
||||
my $win="^ECH: GREASE";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client with GREASE-type ECH");
|
||||
}
|
||||
|
||||
sub lots_of_options_test {
|
||||
print("\n\nLots of options ECH test.\n");
|
||||
my $tt="lots-of-options";
|
||||
my $win="^ECH: success";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client with lots of ECH options");
|
||||
}
|
||||
|
||||
sub no_outer_test {
|
||||
print("\n\nNo outer SNI test.\n");
|
||||
my $tt = "no-outer";
|
||||
my $win = "^ECH: success";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client with no outer SNI ECH");
|
||||
}
|
||||
|
||||
sub cid_free_test {
|
||||
print("\n\nIgnore CIDs test.\n");
|
||||
my $tt = "cid-free";
|
||||
my $win = "^ECH: success";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client/s_server with no CID/trial decrypt");
|
||||
}
|
||||
|
||||
sub cid_wrong_test {
|
||||
print("\n\nIgnore CIDs test.\n");
|
||||
my $tt = "cid-wrong";
|
||||
my $win = "^ECH: failed";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client/s_server with no CID/no trial decrypt");
|
||||
}
|
||||
|
||||
basic_test();
|
||||
wrong_test();
|
||||
grease_test();
|
||||
grease_suite_test();
|
||||
grease_type_test();
|
||||
lots_of_options_test();
|
||||
no_outer_test();
|
||||
cid_free_test();
|
||||
cid_wrong_test();
|
||||
|
||||
Reference in New Issue
Block a user