ech.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 "ssl_local.h"
#include "ech_local.h"
#include "statem/statem_local.h"
#include <openssl/rand.h>
#include <openssl/trace.h>
#include <openssl/evp.h>
#include <openssl/kdf.h>
#ifndef OPENSSL_NO_ECH
/* a size for some crypto vars */
# define OSSL_ECH_CRYPTO_VAR_SIZE 2048
/*
* @brief hash a buffer as a pretend file name being ascii-hex of hashed buffer
* @param es is the OSSL_ECHSTORE we're dealing with
* @param buf is the input buffer
* @param blen is the length of buf
* @param ah_hash is a pointer to where to put the result
* @param ah_len is the length of ah_hash
*/
static int ech_hash_pub_as_fname(OSSL_ECHSTORE *es,
const unsigned char *buf, size_t blen,
char *ah_hash, size_t ah_len)
{
unsigned char hashval[EVP_MAX_MD_SIZE];
size_t hashlen, actual_ah_len;
if (es == NULL
|| EVP_Q_digest(es->libctx, "SHA2-256", es->propq,
buf, blen, hashval, &hashlen) != 1
|| OPENSSL_buf2hexstr_ex(ah_hash, ah_len, &actual_ah_len,
hashval, hashlen, '\0') != 1) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
return 0;
}
return 1;
}
/*
* API calls built around OSSL_ECHSTORE
*/
OSSL_ECHSTORE *OSSL_ECHSTORE_new(OSSL_LIB_CTX *libctx, const char *propq)
{
OSSL_ECHSTORE *es = NULL;
es = OPENSSL_zalloc(sizeof(*es));
if (es == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
return 0;
}
es->libctx = libctx;
es->propq = propq;
return es;
}
static void ossl_echext_free(OSSL_ECHEXT *e)
{
if (e == NULL)
return;
OPENSSL_free(e->val);
OPENSSL_free(e);
return;
}
static void ossl_echstore_entry_free(OSSL_ECHSTORE_ENTRY *ee)
{
if (ee == NULL)
return;
OPENSSL_free(ee->public_name);
OPENSSL_free(ee->pub);
OPENSSL_free(ee->pemfname);
EVP_PKEY_free(ee->keyshare);
OPENSSL_free(ee->encoded);
OPENSSL_free(ee->suites);
sk_OSSL_ECHEXT_pop_free(ee->exts, ossl_echext_free);
OPENSSL_free(ee);
return;
}
void OSSL_ECHSTORE_free(OSSL_ECHSTORE *es)
{
if (es == NULL)
return;
sk_OSSL_ECHSTORE_ENTRY_pop_free(es->entries, ossl_echstore_entry_free);
OPENSSL_free(es);
return;
}
int OSSL_ECHSTORE_new_config(OSSL_ECHSTORE *es,
uint16_t echversion, uint8_t max_name_length,
const char *public_name, OSSL_HPKE_SUITE suite)
{
size_t pnlen = 0;
size_t publen = OSSL_ECH_CRYPTO_VAR_SIZE;
unsigned char pub[OSSL_ECH_CRYPTO_VAR_SIZE];
int rv = 0;
unsigned char *bp = NULL;
size_t bblen = 0;
EVP_PKEY *privp = NULL;
uint8_t config_id = 0;
WPACKET epkt;
BUF_MEM *epkt_mem = NULL;
OSSL_ECHSTORE_ENTRY *ee = NULL;
char pembuf[2 * EVP_MAX_MD_SIZE + 1];
size_t pembuflen = 2 * EVP_MAX_MD_SIZE + 1;
/* basic checks */
if (es == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
pnlen = (public_name == NULL ? 0 : strlen(public_name));
if (pnlen == 0 || pnlen > OSSL_ECH_MAX_PUBLICNAME
|| max_name_length > OSSL_ECH_MAX_MAXNAMELEN) {
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
/* this used have more versions and will again in future */
switch (echversion) {
case OSSL_ECH_RFCXXXX_VERSION:
break;
default:
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
/* so WPACKET_cleanup() won't go wrong */
memset(&epkt, 0, sizeof(epkt));
/* random config_id */
if (RAND_bytes_ex(es->libctx, (unsigned char *)&config_id, 1,
RAND_DRBG_STRENGTH) <= 0) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
/* key pair */
if (OSSL_HPKE_keygen(suite, pub, &publen, &privp, NULL, 0,
es->libctx, es->propq) != 1) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
/*
* Reminder, for draft-13 we want this:
*
* opaque HpkePublicKey<1..2^16-1>;
* uint16 HpkeKemId; // Defined in I-D.irtf-cfrg-hpke
* uint16 HpkeKdfId; // Defined in I-D.irtf-cfrg-hpke
* uint16 HpkeAeadId; // Defined in I-D.irtf-cfrg-hpke
* struct {
* HpkeKdfId kdf_id;
* HpkeAeadId aead_id;
* } HpkeSymmetricCipherSuite;
* struct {
* uint8 config_id;
* HpkeKemId kem_id;
* HpkePublicKey public_key;
* HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>;
* } HpkeKeyConfig;
* struct {
* HpkeKeyConfig key_config;
* uint8 maximum_name_length;
* opaque public_name<1..255>;
* Extension extensions<0..2^16-1>;
* } ECHConfigContents;
* struct {
* uint16 version;
* uint16 length;
* select (ECHConfig.version) {
* case 0xfe0d: ECHConfigContents contents;
* }
* } ECHConfig;
* ECHConfig ECHConfigList<1..2^16-1>;
*/
if ((epkt_mem = BUF_MEM_new()) == NULL
|| !BUF_MEM_grow(epkt_mem, OSSL_ECH_MAX_ECHCONFIG_LEN)) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
/* config id, KEM, public, KDF, AEAD, max name len, public_name, exts */
if (!WPACKET_init(&epkt, epkt_mem)
|| (bp = WPACKET_get_curr(&epkt)) == NULL
|| !WPACKET_start_sub_packet_u16(&epkt)
|| !WPACKET_put_bytes_u16(&epkt, echversion)
|| !WPACKET_start_sub_packet_u16(&epkt)
|| !WPACKET_put_bytes_u8(&epkt, config_id)
|| !WPACKET_put_bytes_u16(&epkt, suite.kem_id)
|| !WPACKET_start_sub_packet_u16(&epkt)
|| !WPACKET_memcpy(&epkt, pub, publen)
|| !WPACKET_close(&epkt)
|| !WPACKET_start_sub_packet_u16(&epkt)
|| !WPACKET_put_bytes_u16(&epkt, suite.kdf_id)
|| !WPACKET_put_bytes_u16(&epkt, suite.aead_id)
|| !WPACKET_close(&epkt)
|| !WPACKET_put_bytes_u8(&epkt, max_name_length)
|| !WPACKET_start_sub_packet_u8(&epkt)
|| !WPACKET_memcpy(&epkt, public_name, pnlen)
|| !WPACKET_close(&epkt)
|| !WPACKET_start_sub_packet_u16(&epkt)
|| !WPACKET_memcpy(&epkt, NULL, 0) /* no extensions */
|| !WPACKET_close(&epkt)
|| !WPACKET_close(&epkt)
|| !WPACKET_close(&epkt)) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
/* bp, bblen has encoding */
WPACKET_get_total_written(&epkt, &bblen);
if ((ee = OPENSSL_zalloc(sizeof(*ee))) == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
ee->suites = OPENSSL_malloc(sizeof(OSSL_HPKE_SUITE));
if (ee->suites == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
if (ech_hash_pub_as_fname(es, pub, publen, pembuf, pembuflen) != 1) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
ee->version = echversion;
ee->pub_len = publen;
ee->pub = OPENSSL_memdup(pub, publen);
if (ee->pub == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
ee->nsuites = 1;
ee->suites[0] = suite;
ee->public_name = OPENSSL_strdup(public_name);
if (ee->public_name == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
ee->max_name_length = max_name_length;
ee->config_id = config_id;
ee->keyshare = privp;
ee->encoded = OPENSSL_memdup(bp, bblen);
if (ee->encoded == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
ee->encoded_len = bblen;
ee->pemfname = OPENSSL_strdup(pembuf);
if (ee->pemfname == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
ee->loadtime = time(0);
/* push entry into store */
if (es->entries == NULL)
es->entries = sk_OSSL_ECHSTORE_ENTRY_new_null();
if (es->entries == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
if (!sk_OSSL_ECHSTORE_ENTRY_push(es->entries, ee)) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
WPACKET_finish(&epkt);
BUF_MEM_free(epkt_mem);
return 1;
err:
EVP_PKEY_free(privp);
WPACKET_cleanup(&epkt);
BUF_MEM_free(epkt_mem);
ossl_echstore_entry_free(ee);
OPENSSL_free(ee);
return rv;
}
int OSSL_ECHSTORE_write_pem(OSSL_ECHSTORE *es, int index, BIO *out)
{
OSSL_ECHSTORE_ENTRY *ee = NULL;
int rv = 0, num = 0, chosen = 0;
if (es == 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;
}
num = sk_OSSL_ECHSTORE_ENTRY_num(es->entries);
if (num <= 0) {
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
if (index >= num) {
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
if (index == OSSL_ECHSTORE_LAST)
chosen = num - 1;
else
chosen = index;
ee = sk_OSSL_ECHSTORE_ENTRY_value(es->entries, chosen);
if (ee == NULL || ee->keyshare == NULL || ee->encoded == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
return 0;
}
/* private key first */
if (!PEM_write_bio_PrivateKey(out, ee->keyshare, NULL, NULL, 0,
NULL, NULL)) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
if (PEM_write_bio(out, PEM_STRING_ECHCONFIG, NULL,
ee->encoded, ee->encoded_len) <= 0) {
ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
goto err;
}
rv = 1;
err:
return rv;
}
int OSSL_ECHSTORE_read_echconfiglist(OSSL_ECHSTORE *es, BIO *in)
{
return 0;
}
int OSSL_ECHSTORE_get1_info(OSSL_ECHSTORE *es, OSSL_ECH_INFO **info,
int *count)
{
return 0;
}
int OSSL_ECHSTORE_downselect(OSSL_ECHSTORE *es, int index)
{
return 0;
}
int OSSL_ECHSTORE_set1_key_and_read_pem(OSSL_ECHSTORE *es, EVP_PKEY *priv,
BIO *in, int for_retry)
{
return 0;
}
int OSSL_ECHSTORE_read_pem(OSSL_ECHSTORE *es, BIO *in, int for_retry)
{
return 0;
}
int OSSL_ECHSTORE_num_keys(OSSL_ECHSTORE *es, int *numkeys)
{
return 0;
}
int OSSL_ECHSTORE_flush_keys(OSSL_ECHSTORE *es, time_t age)
{
return 0;
}
void OSSL_ECH_INFO_free(OSSL_ECH_INFO *info, int count)
{
return;
}
int OSSL_ECH_INFO_print(BIO *out, OSSL_ECH_INFO *info, int count)
{
return 0;
}
int SSL_CTX_set1_echstore(SSL_CTX *ctx, OSSL_ECHSTORE *es)
{
return 0;
}
int SSL_set1_echstore(SSL *s, OSSL_ECHSTORE *es)
{
return 0;
}
OSSL_ECHSTORE *SSL_CTX_get1_echstore(const SSL_CTX *ctx)
{
return NULL;
}
OSSL_ECHSTORE *SSL_get1_echstore(const SSL *s)
{
return NULL;
}
int SSL_ech_set_server_names(SSL *s, const char *inner_name,
const char *outer_name, int no_outer)
{
return 0;
}
int SSL_ech_set_outer_server_name(SSL *s, const char *outer_name, int no_outer)
{
return 0;
}
int SSL_ech_set_outer_alpn_protos(SSL *s, const unsigned char *protos,
const size_t protos_len)
{
return 0;
}
int SSL_ech_get1_status(SSL *s, char **inner_sni, char **outer_sni)
{
return 0;
}
int SSL_ech_set_grease_suite(SSL *s, const char *suite)
{
return 0;
}
int SSL_ech_set_grease_type(SSL *s, uint16_t type)
{
return 0;
}
void SSL_ech_set_callback(SSL *s, SSL_ech_cb_func f)
{
return;
}
int SSL_ech_get_retry_config(SSL *s, unsigned char **ec, size_t *eclen)
{
return 0;
}
int SSL_CTX_ech_set_outer_alpn_protos(SSL_CTX *s, const unsigned char *protos,
const size_t protos_len)
{
return 0;
}
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)
{
return 0;
}
void SSL_CTX_ech_set_callback(SSL_CTX *ctx, SSL_ech_cb_func f)
{
return;
}
#endif