openssl/apps/ech.c

278 lines
9.2 KiB
C

/*
* Copyright 2024 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
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "apps.h"
#include "progs.h"
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/bn.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/hpke.h>
#include <openssl/objects.h>
#include <openssl/x509.h>
#ifndef OPENSSL_NO_ECH
# define OSSL_ECH_KEYGEN_MODE 0 /* default: generate a key pair/ECHConfig */
# define OSSL_ECH_SELPRINT_MODE 1 /* we can print/down-select ECHConfigList */
# define OSSL_ECH_MAXINFILES 5 /* we'll only take this many inputs */
typedef enum OPTION_choice {
/* standard openssl options */
OPT_ERR = -1, OPT_EOF = 0, OPT_HELP, OPT_VERBOSE, OPT_TEXT,
OPT_OUT, OPT_IN,
/* ECHConfig specifics */
OPT_PUBLICNAME, OPT_ECHVERSION,
OPT_MAXNAMELENGTH, OPT_HPKESUITE,
OPT_SELECT
} OPTION_CHOICE;
const OPTIONS ech_options[] = {
OPT_SECTION("General options"),
{"help", OPT_HELP, '-', "Display this summary"},
{"verbose", OPT_VERBOSE, '-', "Provide additional output"},
{"text", OPT_TEXT, '-', "Provide human-readable output"},
OPT_SECTION("Key generation"),
{"out", OPT_OUT, '>',
"Private key and/or ECHConfig [default: echconfig.pem]"},
{"public_name", OPT_PUBLICNAME, 's', "public_name value"},
{"max_name_len", OPT_MAXNAMELENGTH, 'n',
"Maximum host name length value [default: 0]"},
{"suite", OPT_HPKESUITE, 's', "HPKE ciphersuite: e.g. \"0x20,1,3\""},
{"ech_version", OPT_ECHVERSION, 'n',
"ECHConfig version [default: 0xff0d (13)]"},
OPT_SECTION("ECH PEM file downselect/display"),
{"in", OPT_IN, '<', "An ECH PEM file"},
{"select", OPT_SELECT, 'n', "Downselect to the numbered ECH config"},
{NULL}
};
/**
* @brief map version string like 0xff01 or 65291 to uint16_t
* @param arg is the version string, from command line
* @return is the uint16_t value (with zero for error cases)
*/
static uint16_t verstr2us(char *arg)
{
long lv = strtol(arg, NULL, 0);
uint16_t rv = 0;
if (lv < 0xffff && lv > 0)
rv = (uint16_t)lv;
return rv;
}
int ech_main(int argc, char **argv)
{
char *prog = NULL;
OPTION_CHOICE o;
int i, rv = 1, verbose = 0, text = 0, outsupp = 0;
int select = OSSL_ECHSTORE_ALL, numinfiles = 0;
char *outfile = NULL, *infile = NULL;
char *infiles[OSSL_ECH_MAXINFILES] = { NULL };
char *public_name = NULL, *suitestr = NULL;
uint16_t ech_version = OSSL_ECH_CURRENT_VERSION;
uint8_t max_name_length = 0;
OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT;
int mode = OSSL_ECH_KEYGEN_MODE; /* key generation */
OSSL_ECHSTORE *es = NULL;
BIO *ecf = NULL;
prog = opt_init(argc, argv, ech_options);
while ((o = opt_next()) != OPT_EOF) {
switch (o) {
case OPT_EOF:
case OPT_ERR:
BIO_printf(bio_err, "%s: Use -help for summary.\n", prog);
goto end;
case OPT_HELP:
opt_help(ech_options);
rv = 0;
goto end;
case OPT_VERBOSE:
verbose = 1;
break;
case OPT_TEXT:
text = 1;
break;
case OPT_SELECT:
mode = OSSL_ECH_SELPRINT_MODE;
select = strtol(opt_arg(), NULL, 10);
break;
case OPT_OUT:
outfile = opt_arg();
outsupp = 1;
break;
case OPT_IN:
mode = OSSL_ECH_SELPRINT_MODE;
infile = opt_arg();
if (numinfiles >= OSSL_ECH_MAXINFILES) {
BIO_printf(bio_err, "too many input files, only %d allowed\n",
OSSL_ECH_MAXINFILES);
goto opthelp;
}
infiles[numinfiles] = infile;
numinfiles++;
break;
case OPT_PUBLICNAME:
public_name = opt_arg();
break;
case OPT_ECHVERSION:
ech_version = verstr2us(opt_arg());
break;
case OPT_MAXNAMELENGTH:
{
long tmp = strtol(opt_arg(), NULL, 10);
if (tmp < 0 || tmp > OSSL_ECH_MAX_MAXNAMELEN) {
BIO_printf(bio_err,
"max name length out of range [0,%d] (%ld)\n",
OSSL_ECH_MAX_MAXNAMELEN, tmp);
goto opthelp;
} else {
max_name_length = (uint8_t)tmp;
}
}
break;
case OPT_HPKESUITE:
suitestr = opt_arg();
break;
}
}
argc = opt_num_rest();
argv = opt_rest();
if (argc != 0) {
BIO_printf(bio_err, "%s: Unknown parameter %s\n", prog, argv[0]);
goto opthelp;
}
/* Check ECH-specific inputs */
switch (ech_version) {
case OSSL_ECH_RFCXXXX_VERSION: /* fall through */
case 13:
ech_version = OSSL_ECH_RFCXXXX_VERSION;
break;
default:
BIO_printf(bio_err, "Un-supported version (0x%04x)\n", ech_version);
goto end;
}
if (max_name_length > OSSL_ECH_MAX_MAXNAMELEN) {
BIO_printf(bio_err, "Weird max name length (0x%04x) - biggest is "
"(0x%04x) - exiting\n", max_name_length,
OSSL_ECH_MAX_MAXNAMELEN);
ERR_print_errors(bio_err);
goto end;
}
if (suitestr != NULL) {
if (OSSL_HPKE_str2suite(suitestr, &hpke_suite) != 1) {
BIO_printf(bio_err, "Bad OSSL_HPKE_SUITE (%s)\n", suitestr);
ERR_print_errors(bio_err);
goto end;
}
}
/* Set default if needed */
if (outfile == NULL)
outfile = "echconfig.pem";
es = OSSL_ECHSTORE_new(NULL, NULL);
if (es == NULL)
goto end;
if (mode == OSSL_ECH_KEYGEN_MODE) {
if (verbose)
BIO_printf(bio_err, "Calling OSSL_ECHSTORE_new_config\n");
if ((ecf = BIO_new_file(outfile, "w")) == NULL
|| OSSL_ECHSTORE_new_config(es, ech_version, max_name_length,
public_name, hpke_suite) != 1
|| OSSL_ECHSTORE_write_pem(es, 0, ecf) != 1) {
BIO_printf(bio_err, "OSSL_ECHSTORE_new_config error\n");
goto end;
}
if (verbose)
BIO_printf(bio_err, "OSSL_ECHSTORE_new_config success\n");
rv = 0;
}
if (mode == OSSL_ECH_SELPRINT_MODE) {
if (numinfiles == 0)
goto opthelp;
for (i = 0; i != numinfiles; i++) {
if ((ecf = BIO_new_file(infiles[i], "r")) == NULL
|| OSSL_ECHSTORE_read_pem(es, ecf, OSSL_ECH_FOR_RETRY) != 1) {
if (verbose)
BIO_printf(bio_err, "OSSL_ECHSTORE_read_pem error for %s\n",
infiles[i]);
/* try read it as an ECHConfigList */
goto end;
}
BIO_free(ecf);
ecf = NULL;
}
if (verbose)
BIO_printf(bio_err, "Success reading %d files\n", numinfiles);
if (outsupp == 1) {
/* write result to that, with downselection if required */
if (verbose)
BIO_printf(bio_err, "Will write to %s\n", outfile);
if (verbose && select != OSSL_ECHSTORE_ALL)
BIO_printf(bio_err, "Selected entry: %d\n", select);
if ((ecf = BIO_new_file(outfile, "w")) == NULL
|| OSSL_ECHSTORE_write_pem(es, select, ecf) != 1) {
BIO_printf(bio_err, "OSSL_ECHSTORE_write_pem error\n");
goto end;
}
if (verbose)
BIO_printf(bio_err, "Success writing to %s\n", outfile);
}
rv = 0;
}
if (text) {
int oi_ind, oi_cnt = 0;
if (OSSL_ECHSTORE_num_entries(es, &oi_cnt) != 1)
goto end;
if (verbose)
BIO_printf(bio_err, "Printing %d ECHConfigList\n", oi_cnt);
for (oi_ind = 0; oi_ind != oi_cnt; oi_ind++) {
time_t secs = 0;
char *pn = NULL, *ec = NULL;
int has_priv, for_retry;
if (OSSL_ECHSTORE_get1_info(es, oi_ind, &secs, &pn, &ec,
&has_priv, &for_retry) != 1) {
OPENSSL_free(pn); /* just in case */
OPENSSL_free(ec);
goto end;
}
BIO_printf(bio_err, "ECH entry: %d public_name: %s age: %lld%s\n",
oi_ind, pn, (long long)secs,
has_priv ? " (has private key)" : "");
BIO_printf(bio_err, "\t%s\n", ec);
OPENSSL_free(pn);
OPENSSL_free(ec);
}
if (verbose)
BIO_printf(bio_err, "Success printing %d ECHConfigList\n", oi_cnt);
rv = 0;
}
end:
OSSL_ECHSTORE_free(es);
BIO_free_all(ecf);
return rv;
opthelp:
BIO_printf(bio_err, "%s: Use -help for summary.\n", prog);
BIO_printf(bio_err, "\tup to %d -in instances allowed\n", OSSL_ECH_MAXINFILES);
return rv;
}
#endif