diff options
Diffstat (limited to 'apps/lib')
-rw-r--r-- | apps/lib/app_libctx.c | 48 | ||||
-rw-r--r-- | apps/lib/app_params.c | 132 | ||||
-rw-r--r-- | apps/lib/app_provider.c | 92 | ||||
-rw-r--r-- | apps/lib/app_rand.c | 124 | ||||
-rw-r--r-- | apps/lib/app_x509.c | 137 | ||||
-rw-r--r-- | apps/lib/apps.c | 3400 | ||||
-rw-r--r-- | apps/lib/apps_ui.c | 223 | ||||
-rw-r--r-- | apps/lib/build.info | 23 | ||||
-rw-r--r-- | apps/lib/cmp_mock_srv.c | 452 | ||||
-rw-r--r-- | apps/lib/columns.c | 27 | ||||
-rw-r--r-- | apps/lib/engine.c | 193 | ||||
-rw-r--r-- | apps/lib/engine_loader.c | 203 | ||||
-rw-r--r-- | apps/lib/fmt.c | 15 | ||||
-rw-r--r-- | apps/lib/http_server.c | 536 | ||||
-rw-r--r-- | apps/lib/names.c | 45 | ||||
-rw-r--r-- | apps/lib/opt.c | 1203 | ||||
-rw-r--r-- | apps/lib/s_cb.c | 1592 | ||||
-rw-r--r-- | apps/lib/s_socket.c | 462 | ||||
-rw-r--r-- | apps/lib/tlssrp_depr.c | 231 | ||||
-rw-r--r-- | apps/lib/vms_decc_argv.c | 72 | ||||
-rw-r--r-- | apps/lib/vms_term_sock.c | 591 | ||||
-rw-r--r-- | apps/lib/win32_init.c | 307 |
22 files changed, 10108 insertions, 0 deletions
diff --git a/apps/lib/app_libctx.c b/apps/lib/app_libctx.c new file mode 100644 index 000000000000..4b9ec40e8527 --- /dev/null +++ b/apps/lib/app_libctx.c @@ -0,0 +1,48 @@ +/* + * Copyright 1995-2021 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 "app_libctx.h" +#include "apps.h" + +static OSSL_LIB_CTX *app_libctx = NULL; +static const char *app_propq = NULL; + +int app_set_propq(const char *arg) +{ + app_propq = arg; + return 1; +} + +const char *app_get0_propq(void) +{ + return app_propq; +} + +OSSL_LIB_CTX *app_get0_libctx(void) +{ + return app_libctx; +} + +OSSL_LIB_CTX *app_create_libctx(void) +{ + /* + * Load the NULL provider into the default library context and create a + * library context which will then be used for any OPT_PROV options. + */ + if (app_libctx == NULL) { + if (!app_provider_load(NULL, "null")) { + opt_printf_stderr( "Failed to create null provider\n"); + return NULL; + } + app_libctx = OSSL_LIB_CTX_new(); + } + if (app_libctx == NULL) + opt_printf_stderr("Failed to create library context\n"); + return app_libctx; +} + diff --git a/apps/lib/app_params.c b/apps/lib/app_params.c new file mode 100644 index 000000000000..95e1298ee926 --- /dev/null +++ b/apps/lib/app_params.c @@ -0,0 +1,132 @@ +/* + * Copyright 2019-2020 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 "apps.h" +#include "app_params.h" + +static int describe_param_type(char *buf, size_t bufsz, const OSSL_PARAM *param) +{ + const char *type_mod = ""; + const char *type = NULL; + int show_type_number = 0; + int printed_len; + + switch (param->data_type) { + case OSSL_PARAM_UNSIGNED_INTEGER: + type_mod = "unsigned "; + /* FALLTHRU */ + case OSSL_PARAM_INTEGER: + type = "integer"; + break; + case OSSL_PARAM_UTF8_PTR: + type_mod = "pointer to a "; + /* FALLTHRU */ + case OSSL_PARAM_UTF8_STRING: + type = "UTF8 encoded string"; + break; + case OSSL_PARAM_OCTET_PTR: + type_mod = "pointer to an "; + /* FALLTHRU */ + case OSSL_PARAM_OCTET_STRING: + type = "octet string"; + break; + default: + type = "unknown type"; + show_type_number = 1; + break; + } + + printed_len = BIO_snprintf(buf, bufsz, "%s: ", param->key); + if (printed_len > 0) { + buf += printed_len; + bufsz -= printed_len; + } + printed_len = BIO_snprintf(buf, bufsz, "%s%s", type_mod, type); + if (printed_len > 0) { + buf += printed_len; + bufsz -= printed_len; + } + if (show_type_number) { + printed_len = BIO_snprintf(buf, bufsz, " [%d]", param->data_type); + if (printed_len > 0) { + buf += printed_len; + bufsz -= printed_len; + } + } + if (param->data_size == 0) + printed_len = BIO_snprintf(buf, bufsz, " (arbitrary size)"); + else + printed_len = BIO_snprintf(buf, bufsz, " (max %zu bytes large)", + param->data_size); + if (printed_len > 0) { + buf += printed_len; + bufsz -= printed_len; + } + *buf = '\0'; + return 1; +} + +int print_param_types(const char *thing, const OSSL_PARAM *pdefs, int indent) +{ + if (pdefs == NULL) { + return 1; + } else if (pdefs->key == NULL) { + /* + * An empty list? This shouldn't happen, but let's just make sure to + * say something if there's a badly written provider... + */ + BIO_printf(bio_out, "%*sEmpty list of %s (!!!)\n", indent, "", thing); + } else { + BIO_printf(bio_out, "%*s%s:\n", indent, "", thing); + for (; pdefs->key != NULL; pdefs++) { + char buf[200]; /* This should be ample space */ + + describe_param_type(buf, sizeof(buf), pdefs); + BIO_printf(bio_out, "%*s %s\n", indent, "", buf); + } + } + return 1; +} + +void print_param_value(const OSSL_PARAM *p, int indent) +{ + int64_t i; + uint64_t u; + + printf("%*s%s: ", indent, "", p->key); + switch (p->data_type) { + case OSSL_PARAM_UNSIGNED_INTEGER: + if (OSSL_PARAM_get_uint64(p, &u)) + BIO_printf(bio_out, "%llu\n", (unsigned long long int)u); + else + BIO_printf(bio_out, "error getting value\n"); + break; + case OSSL_PARAM_INTEGER: + if (OSSL_PARAM_get_int64(p, &i)) + BIO_printf(bio_out, "%lld\n", (long long int)i); + else + BIO_printf(bio_out, "error getting value\n"); + break; + case OSSL_PARAM_UTF8_PTR: + BIO_printf(bio_out, "'%s'\n", *(char **)(p->data)); + break; + case OSSL_PARAM_UTF8_STRING: + BIO_printf(bio_out, "'%s'\n", (char *)p->data); + break; + case OSSL_PARAM_OCTET_PTR: + case OSSL_PARAM_OCTET_STRING: + BIO_printf(bio_out, "<%zu bytes>\n", p->data_size); + break; + default: + BIO_printf(bio_out, "unknown type (%u) of %zu bytes\n", + p->data_type, p->data_size); + break; + } +} + diff --git a/apps/lib/app_provider.c b/apps/lib/app_provider.c new file mode 100644 index 000000000000..63f78ae07d80 --- /dev/null +++ b/apps/lib/app_provider.c @@ -0,0 +1,92 @@ +/* + * Copyright 2020-2021 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 "apps.h" +#include <string.h> +#include <openssl/err.h> +#include <openssl/provider.h> +#include <openssl/safestack.h> + +/* Non-zero if any of the provider options have been seen */ +static int provider_option_given = 0; + +DEFINE_STACK_OF(OSSL_PROVIDER) + +/* + * See comments in opt_verify for explanation of this. + */ +enum prov_range { OPT_PROV_ENUM }; + +static STACK_OF(OSSL_PROVIDER) *app_providers = NULL; + +static void provider_free(OSSL_PROVIDER *prov) +{ + OSSL_PROVIDER_unload(prov); +} + +int app_provider_load(OSSL_LIB_CTX *libctx, const char *provider_name) +{ + OSSL_PROVIDER *prov; + + prov = OSSL_PROVIDER_load(libctx, provider_name); + if (prov == NULL) { + opt_printf_stderr("%s: unable to load provider %s\n" + "Hint: use -provider-path option or OPENSSL_MODULES environment variable.\n", + opt_getprog(), provider_name); + ERR_print_errors(bio_err); + return 0; + } + if (app_providers == NULL) + app_providers = sk_OSSL_PROVIDER_new_null(); + if (app_providers == NULL + || !sk_OSSL_PROVIDER_push(app_providers, prov)) { + app_providers_cleanup(); + return 0; + } + return 1; +} + +void app_providers_cleanup(void) +{ + sk_OSSL_PROVIDER_pop_free(app_providers, provider_free); + app_providers = NULL; +} + +static int opt_provider_path(const char *path) +{ + if (path != NULL && *path == '\0') + path = NULL; + return OSSL_PROVIDER_set_default_search_path(app_get0_libctx(), path); +} + +int opt_provider(int opt) +{ + const int given = provider_option_given; + + provider_option_given = 1; + switch ((enum prov_range)opt) { + case OPT_PROV__FIRST: + case OPT_PROV__LAST: + return 1; + case OPT_PROV_PROVIDER: + return app_provider_load(app_get0_libctx(), opt_arg()); + case OPT_PROV_PROVIDER_PATH: + return opt_provider_path(opt_arg()); + case OPT_PROV_PROPQUERY: + return app_set_propq(opt_arg()); + } + /* Should never get here but if we do, undo what we did earlier */ + provider_option_given = given; + return 0; +} + +int opt_provider_option_given(void) +{ + return provider_option_given; +} diff --git a/apps/lib/app_rand.c b/apps/lib/app_rand.c new file mode 100644 index 000000000000..713792ead40a --- /dev/null +++ b/apps/lib/app_rand.c @@ -0,0 +1,124 @@ +/* + * Copyright 1995-2021 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 "apps.h" +#include <openssl/bio.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/conf.h> + +static char *save_rand_file; +static STACK_OF(OPENSSL_STRING) *randfiles; + +void app_RAND_load_conf(CONF *c, const char *section) +{ + const char *randfile = NCONF_get_string(c, section, "RANDFILE"); + + if (randfile == NULL) { + ERR_clear_error(); + return; + } + if (RAND_load_file(randfile, -1) < 0) { + BIO_printf(bio_err, "Can't load %s into RNG\n", randfile); + ERR_print_errors(bio_err); + } + if (save_rand_file == NULL) { + save_rand_file = OPENSSL_strdup(randfile); + /* If some internal memory errors have occurred */ + if (save_rand_file == NULL) { + BIO_printf(bio_err, "Can't duplicate %s\n", randfile); + ERR_print_errors(bio_err); + } + } +} + +static int loadfiles(char *name) +{ + char *p; + int last, ret = 1; + + for ( ; ; ) { + last = 0; + for (p = name; *p != '\0' && *p != LIST_SEPARATOR_CHAR; p++) + continue; + if (*p == '\0') + last = 1; + *p = '\0'; + if (RAND_load_file(name, -1) < 0) { + BIO_printf(bio_err, "Can't load %s into RNG\n", name); + ERR_print_errors(bio_err); + ret = 0; + } + if (last) + break; + name = p + 1; + if (*name == '\0') + break; + } + return ret; +} + +int app_RAND_load(void) +{ + char *p; + int i, ret = 1; + + for (i = 0; i < sk_OPENSSL_STRING_num(randfiles); i++) { + p = sk_OPENSSL_STRING_value(randfiles, i); + if (!loadfiles(p)) + ret = 0; + } + sk_OPENSSL_STRING_free(randfiles); + return ret; +} + +int app_RAND_write(void) +{ + int ret = 1; + + if (save_rand_file == NULL) + return 1; + if (RAND_write_file(save_rand_file) == -1) { + BIO_printf(bio_err, "Cannot write random bytes:\n"); + ERR_print_errors(bio_err); + ret = 0; + } + OPENSSL_free(save_rand_file); + save_rand_file = NULL; + return ret; +} + + +/* + * See comments in opt_verify for explanation of this. + */ +enum r_range { OPT_R_ENUM }; + +int opt_rand(int opt) +{ + switch ((enum r_range)opt) { + case OPT_R__FIRST: + case OPT_R__LAST: + break; + case OPT_R_RAND: + if (randfiles == NULL + && (randfiles = sk_OPENSSL_STRING_new_null()) == NULL) + return 0; + if (!sk_OPENSSL_STRING_push(randfiles, opt_arg())) + return 0; + break; + case OPT_R_WRITERAND: + OPENSSL_free(save_rand_file); + save_rand_file = OPENSSL_strdup(opt_arg()); + if (save_rand_file == NULL) + return 0; + break; + } + return 1; +} diff --git a/apps/lib/app_x509.c b/apps/lib/app_x509.c new file mode 100644 index 000000000000..f2c22948f201 --- /dev/null +++ b/apps/lib/app_x509.c @@ -0,0 +1,137 @@ +/* + * Copyright 2020-2021 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 <string.h> +#include "apps.h" + +/* + * X509_ctrl_str() is sorely lacking in libcrypto, but is still needed to + * allow the application to process verification options in a manner similar + * to signature or other options that pass through EVP_PKEY_CTX_ctrl_str(), + * for uniformity. + * + * As soon as more stuff is added, the code will need serious rework. For + * the moment, it only handles the FIPS 196 / SM2 distinguishing ID. + */ +#ifdef EVP_PKEY_CTRL_SET1_ID +static ASN1_OCTET_STRING *mk_octet_string(void *value, size_t value_n) +{ + ASN1_OCTET_STRING *v = ASN1_OCTET_STRING_new(); + + if (v == NULL) { + BIO_printf(bio_err, "error: allocation failed\n"); + } else if (!ASN1_OCTET_STRING_set(v, value, (int)value_n)) { + ASN1_OCTET_STRING_free(v); + v = NULL; + } + return v; +} +#endif + +static int x509_ctrl(void *object, int cmd, void *value, size_t value_n) +{ + switch (cmd) { +#ifdef EVP_PKEY_CTRL_SET1_ID + case EVP_PKEY_CTRL_SET1_ID: + { + ASN1_OCTET_STRING *v = mk_octet_string(value, value_n); + + if (v == NULL) { + BIO_printf(bio_err, + "error: setting distinguishing ID in certificate failed\n"); + return 0; + } + + X509_set0_distinguishing_id(object, v); + return 1; + } +#endif + default: + break; + } + return -2; /* typical EVP_PKEY return for "unsupported" */ +} + +static int x509_req_ctrl(void *object, int cmd, void *value, size_t value_n) +{ + switch (cmd) { +#ifdef EVP_PKEY_CTRL_SET1_ID + case EVP_PKEY_CTRL_SET1_ID: + { + ASN1_OCTET_STRING *v = mk_octet_string(value, value_n); + + if (v == NULL) { + BIO_printf(bio_err, + "error: setting distinguishing ID in certificate signing request failed\n"); + return 0; + } + + X509_REQ_set0_distinguishing_id(object, v); + return 1; + } +#endif + default: + break; + } + return -2; /* typical EVP_PKEY return for "unsupported" */ +} + +static int do_x509_ctrl_string(int (*ctrl)(void *object, int cmd, + void *value, size_t value_n), + void *object, const char *value) +{ + int rv = 0; + char *stmp, *vtmp = NULL; + size_t vtmp_len = 0; + int cmd = 0; /* Will get command values that make sense somehow */ + + stmp = OPENSSL_strdup(value); + if (stmp == NULL) + return -1; + vtmp = strchr(stmp, ':'); + if (vtmp != NULL) { + *vtmp = 0; + vtmp++; + vtmp_len = strlen(vtmp); + } + + if (strcmp(stmp, "distid") == 0) { +#ifdef EVP_PKEY_CTRL_SET1_ID + cmd = EVP_PKEY_CTRL_SET1_ID; /* ... except we put it in X509 */ +#endif + } else if (strcmp(stmp, "hexdistid") == 0) { + if (vtmp != NULL) { + void *hexid; + long hexid_len = 0; + + hexid = OPENSSL_hexstr2buf((const char *)vtmp, &hexid_len); + OPENSSL_free(stmp); + stmp = vtmp = hexid; + vtmp_len = (size_t)hexid_len; + } +#ifdef EVP_PKEY_CTRL_SET1_ID + cmd = EVP_PKEY_CTRL_SET1_ID; /* ... except we put it in X509 */ +#endif + } + + rv = ctrl(object, cmd, vtmp, vtmp_len); + + OPENSSL_free(stmp); + return rv; +} + +int x509_ctrl_string(X509 *x, const char *value) +{ + return do_x509_ctrl_string(x509_ctrl, x, value); +} + +int x509_req_ctrl_string(X509_REQ *x, const char *value) +{ + return do_x509_ctrl_string(x509_req_ctrl, x, value); +} diff --git a/apps/lib/apps.c b/apps/lib/apps.c new file mode 100644 index 000000000000..a632b0cff2bf --- /dev/null +++ b/apps/lib/apps.c @@ -0,0 +1,3400 @@ +/* + * Copyright 1995-2023 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 + */ + +#if !defined(_POSIX_C_SOURCE) && defined(OPENSSL_SYS_VMS) +/* + * On VMS, you need to define this to get the declaration of fileno(). The + * value 2 is to make sure no function defined in POSIX-2 is left undefined. + */ +# define _POSIX_C_SOURCE 2 +#endif + +#ifndef OPENSSL_NO_ENGINE +/* We need to use some deprecated APIs */ +# define OPENSSL_SUPPRESS_DEPRECATED +# include <openssl/engine.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#ifndef OPENSSL_NO_POSIX_IO +# include <sys/stat.h> +# include <fcntl.h> +#endif +#include <ctype.h> +#include <errno.h> +#include <openssl/err.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/http.h> +#include <openssl/pem.h> +#include <openssl/store.h> +#include <openssl/pkcs12.h> +#include <openssl/ui.h> +#include <openssl/safestack.h> +#include <openssl/rsa.h> +#include <openssl/rand.h> +#include <openssl/bn.h> +#include <openssl/ssl.h> +#include <openssl/store.h> +#include <openssl/core_names.h> +#include "s_apps.h" +#include "apps.h" + +#ifdef _WIN32 +static int WIN32_rename(const char *from, const char *to); +# define rename(from,to) WIN32_rename((from),(to)) +#endif + +#if defined(OPENSSL_SYS_WINDOWS) || defined(OPENSSL_SYS_MSDOS) +# include <conio.h> +#endif + +#if defined(OPENSSL_SYS_MSDOS) && !defined(_WIN32) || defined(__BORLANDC__) +# define _kbhit kbhit +#endif + +static BIO *bio_open_default_(const char *filename, char mode, int format, + int quiet); + +#define PASS_SOURCE_SIZE_MAX 4 + +DEFINE_STACK_OF(CONF) + +typedef struct { + const char *name; + unsigned long flag; + unsigned long mask; +} NAME_EX_TBL; + +static int set_table_opts(unsigned long *flags, const char *arg, + const NAME_EX_TBL * in_tbl); +static int set_multi_opts(unsigned long *flags, const char *arg, + const NAME_EX_TBL * in_tbl); +static +int load_key_certs_crls_suppress(const char *uri, int format, int maybe_stdin, + const char *pass, const char *desc, + EVP_PKEY **ppkey, EVP_PKEY **ppubkey, + EVP_PKEY **pparams, + X509 **pcert, STACK_OF(X509) **pcerts, + X509_CRL **pcrl, STACK_OF(X509_CRL) **pcrls, + int suppress_decode_errors); + +int app_init(long mesgwin); + +int chopup_args(ARGS *arg, char *buf) +{ + int quoted; + char c = '\0', *p = NULL; + + arg->argc = 0; + if (arg->size == 0) { + arg->size = 20; + arg->argv = app_malloc(sizeof(*arg->argv) * arg->size, "argv space"); + } + + for (p = buf;;) { + /* Skip whitespace. */ + while (*p && isspace(_UC(*p))) + p++; + if (*p == '\0') + break; + + /* The start of something good :-) */ + if (arg->argc >= arg->size) { + char **tmp; + arg->size += 20; + tmp = OPENSSL_realloc(arg->argv, sizeof(*arg->argv) * arg->size); + if (tmp == NULL) + return 0; + arg->argv = tmp; + } + quoted = *p == '\'' || *p == '"'; + if (quoted) + c = *p++; + arg->argv[arg->argc++] = p; + + /* now look for the end of this */ + if (quoted) { + while (*p && *p != c) + p++; + *p++ = '\0'; + } else { + while (*p && !isspace(_UC(*p))) + p++; + if (*p) + *p++ = '\0'; + } + } + arg->argv[arg->argc] = NULL; + return 1; +} + +#ifndef APP_INIT +int app_init(long mesgwin) +{ + return 1; +} +#endif + +int ctx_set_verify_locations(SSL_CTX *ctx, + const char *CAfile, int noCAfile, + const char *CApath, int noCApath, + const char *CAstore, int noCAstore) +{ + if (CAfile == NULL && CApath == NULL && CAstore == NULL) { + if (!noCAfile && SSL_CTX_set_default_verify_file(ctx) <= 0) + return 0; + if (!noCApath && SSL_CTX_set_default_verify_dir(ctx) <= 0) + return 0; + if (!noCAstore && SSL_CTX_set_default_verify_store(ctx) <= 0) + return 0; + + return 1; + } + + if (CAfile != NULL && !SSL_CTX_load_verify_file(ctx, CAfile)) + return 0; + if (CApath != NULL && !SSL_CTX_load_verify_dir(ctx, CApath)) + return 0; + if (CAstore != NULL && !SSL_CTX_load_verify_store(ctx, CAstore)) + return 0; + return 1; +} + +#ifndef OPENSSL_NO_CT + +int ctx_set_ctlog_list_file(SSL_CTX *ctx, const char *path) +{ + if (path == NULL) + return SSL_CTX_set_default_ctlog_list_file(ctx); + + return SSL_CTX_set_ctlog_list_file(ctx, path); +} + +#endif + +static unsigned long nmflag = 0; +static char nmflag_set = 0; + +int set_nameopt(const char *arg) +{ + int ret = set_name_ex(&nmflag, arg); + + if (ret) + nmflag_set = 1; + + return ret; +} + +unsigned long get_nameopt(void) +{ + return (nmflag_set) ? nmflag : XN_FLAG_ONELINE; +} + +void dump_cert_text(BIO *out, X509 *x) +{ + print_name(out, "subject=", X509_get_subject_name(x)); + print_name(out, "issuer=", X509_get_issuer_name(x)); +} + +int wrap_password_callback(char *buf, int bufsiz, int verify, void *userdata) +{ + return password_callback(buf, bufsiz, verify, (PW_CB_DATA *)userdata); +} + + +static char *app_get_pass(const char *arg, int keepbio); + +char *get_passwd(const char *pass, const char *desc) +{ + char *result = NULL; + + if (desc == NULL) + desc = "<unknown>"; + if (!app_passwd(pass, NULL, &result, NULL)) + BIO_printf(bio_err, "Error getting password for %s\n", desc); + if (pass != NULL && result == NULL) { + BIO_printf(bio_err, + "Trying plain input string (better precede with 'pass:')\n"); + result = OPENSSL_strdup(pass); + if (result == NULL) + BIO_printf(bio_err, "Out of memory getting password for %s\n", desc); + } + return result; +} + +int app_passwd(const char *arg1, const char *arg2, char **pass1, char **pass2) +{ + int same = arg1 != NULL && arg2 != NULL && strcmp(arg1, arg2) == 0; + + if (arg1 != NULL) { + *pass1 = app_get_pass(arg1, same); + if (*pass1 == NULL) + return 0; + } else if (pass1 != NULL) { + *pass1 = NULL; + } + if (arg2 != NULL) { + *pass2 = app_get_pass(arg2, same ? 2 : 0); + if (*pass2 == NULL) + return 0; + } else if (pass2 != NULL) { + *pass2 = NULL; + } + return 1; +} + +static char *app_get_pass(const char *arg, int keepbio) +{ + static BIO *pwdbio = NULL; + char *tmp, tpass[APP_PASS_LEN]; + int i; + + /* PASS_SOURCE_SIZE_MAX = max number of chars before ':' in below strings */ + if (strncmp(arg, "pass:", 5) == 0) + return OPENSSL_strdup(arg + 5); + if (strncmp(arg, "env:", 4) == 0) { + tmp = getenv(arg + 4); + if (tmp == NULL) { + BIO_printf(bio_err, "No environment variable %s\n", arg + 4); + return NULL; + } + return OPENSSL_strdup(tmp); + } + if (!keepbio || pwdbio == NULL) { + if (strncmp(arg, "file:", 5) == 0) { + pwdbio = BIO_new_file(arg + 5, "r"); + if (pwdbio == NULL) { + BIO_printf(bio_err, "Can't open file %s\n", arg + 5); + return NULL; + } +#if !defined(_WIN32) + /* + * Under _WIN32, which covers even Win64 and CE, file + * descriptors referenced by BIO_s_fd are not inherited + * by child process and therefore below is not an option. + * It could have been an option if bss_fd.c was operating + * on real Windows descriptors, such as those obtained + * with CreateFile. + */ + } else if (strncmp(arg, "fd:", 3) == 0) { + BIO *btmp; + i = atoi(arg + 3); + if (i >= 0) + pwdbio = BIO_new_fd(i, BIO_NOCLOSE); + if ((i < 0) || pwdbio == NULL) { + BIO_printf(bio_err, "Can't access file descriptor %s\n", arg + 3); + return NULL; + } + /* + * Can't do BIO_gets on an fd BIO so add a buffering BIO + */ + btmp = BIO_new(BIO_f_buffer()); + if (btmp == NULL) { + BIO_free_all(pwdbio); + pwdbio = NULL; + BIO_printf(bio_err, "Out of memory\n"); + return NULL; + } + pwdbio = BIO_push(btmp, pwdbio); +#endif + } else if (strcmp(arg, "stdin") == 0) { + unbuffer(stdin); + pwdbio = dup_bio_in(FORMAT_TEXT); + if (pwdbio == NULL) { + BIO_printf(bio_err, "Can't open BIO for stdin\n"); + return NULL; + } + } else { + /* argument syntax error; do not reveal too much about arg */ + tmp = strchr(arg, ':'); + if (tmp == NULL || tmp - arg > PASS_SOURCE_SIZE_MAX) + BIO_printf(bio_err, + "Invalid password argument, missing ':' within the first %d chars\n", + PASS_SOURCE_SIZE_MAX + 1); + else + BIO_printf(bio_err, + "Invalid password argument, starting with \"%.*s\"\n", + (int)(tmp - arg + 1), arg); + return NULL; + } + } + i = BIO_gets(pwdbio, tpass, APP_PASS_LEN); + if (keepbio != 1) { + BIO_free_all(pwdbio); + pwdbio = NULL; + } + if (i <= 0) { + BIO_printf(bio_err, "Error reading password from BIO\n"); + return NULL; + } + tmp = strchr(tpass, '\n'); + if (tmp != NULL) + *tmp = 0; + return OPENSSL_strdup(tpass); +} + +CONF *app_load_config_bio(BIO *in, const char *filename) +{ + long errorline = -1; + CONF *conf; + int i; + + conf = NCONF_new_ex(app_get0_libctx(), NULL); + i = NCONF_load_bio(conf, in, &errorline); + if (i > 0) + return conf; + + if (errorline <= 0) { + BIO_printf(bio_err, "%s: Can't load ", opt_getprog()); + } else { + BIO_printf(bio_err, "%s: Error on line %ld of ", opt_getprog(), + errorline); + } + if (filename != NULL) + BIO_printf(bio_err, "config file \"%s\"\n", filename); + else + BIO_printf(bio_err, "config input"); + + NCONF_free(conf); + return NULL; +} + +CONF *app_load_config_verbose(const char *filename, int verbose) +{ + if (verbose) { + if (*filename == '\0') + BIO_printf(bio_err, "No configuration used\n"); + else + BIO_printf(bio_err, "Using configuration from %s\n", filename); + } + return app_load_config_internal(filename, 0); +} + +CONF *app_load_config_internal(const char *filename, int quiet) +{ + BIO *in; + CONF *conf; + + if (filename == NULL || *filename != '\0') { + if ((in = bio_open_default_(filename, 'r', FORMAT_TEXT, quiet)) == NULL) + return NULL; + conf = app_load_config_bio(in, filename); + BIO_free(in); + } else { + /* Return empty config if filename is empty string. */ + conf = NCONF_new_ex(app_get0_libctx(), NULL); + } + return conf; +} + +int app_load_modules(const CONF *config) +{ + CONF *to_free = NULL; + + if (config == NULL) + config = to_free = app_load_config_quiet(default_config_file); + if (config == NULL) + return 1; + + if (CONF_modules_load(config, NULL, 0) <= 0) { + BIO_printf(bio_err, "Error configuring OpenSSL modules\n"); + ERR_print_errors(bio_err); + NCONF_free(to_free); + return 0; + } + NCONF_free(to_free); + return 1; +} + +int add_oid_section(CONF *conf) +{ + char *p; + STACK_OF(CONF_VALUE) *sktmp; + CONF_VALUE *cnf; + int i; + + if ((p = NCONF_get_string(conf, NULL, "oid_section")) == NULL) { + ERR_clear_error(); + return 1; + } + if ((sktmp = NCONF_get_section(conf, p)) == NULL) { + BIO_printf(bio_err, "problem loading oid section %s\n", p); + return 0; + } + for (i = 0; i < sk_CONF_VALUE_num(sktmp); i++) { + cnf = sk_CONF_VALUE_value(sktmp, i); + if (OBJ_create(cnf->value, cnf->name, cnf->name) == NID_undef) { + BIO_printf(bio_err, "problem creating object %s=%s\n", + cnf->name, cnf->value); + return 0; + } + } + return 1; +} + +CONF *app_load_config_modules(const char *configfile) +{ + CONF *conf = NULL; + + if (configfile != NULL) { + if ((conf = app_load_config_verbose(configfile, 1)) == NULL) + return NULL; + if (configfile != default_config_file && !app_load_modules(conf)) { + NCONF_free(conf); + conf = NULL; + } + } + return conf; +} + +#define IS_HTTP(uri) ((uri) != NULL \ + && strncmp(uri, OSSL_HTTP_PREFIX, strlen(OSSL_HTTP_PREFIX)) == 0) +#define IS_HTTPS(uri) ((uri) != NULL \ + && strncmp(uri, OSSL_HTTPS_PREFIX, strlen(OSSL_HTTPS_PREFIX)) == 0) + +X509 *load_cert_pass(const char *uri, int format, int maybe_stdin, + const char *pass, const char *desc) +{ + X509 *cert = NULL; + + if (desc == NULL) + desc = "certificate"; + if (IS_HTTPS(uri)) + BIO_printf(bio_err, "Loading %s over HTTPS is unsupported\n", desc); + else if (IS_HTTP(uri)) + cert = X509_load_http(uri, NULL, NULL, 0 /* timeout */); + else + (void)load_key_certs_crls(uri, format, maybe_stdin, pass, desc, + NULL, NULL, NULL, &cert, NULL, NULL, NULL); + if (cert == NULL) { + BIO_printf(bio_err, "Unable to load %s\n", desc); + ERR_print_errors(bio_err); + } + return cert; +} + +X509_CRL *load_crl(const char *uri, int format, int maybe_stdin, + const char *desc) +{ + X509_CRL *crl = NULL; + + if (desc == NULL) + desc = "CRL"; + if (IS_HTTPS(uri)) + BIO_printf(bio_err, "Loading %s over HTTPS is unsupported\n", desc); + else if (IS_HTTP(uri)) + crl = X509_CRL_load_http(uri, NULL, NULL, 0 /* timeout */); + else + (void)load_key_certs_crls(uri, format, maybe_stdin, NULL, desc, + NULL, NULL, NULL, NULL, NULL, &crl, NULL); + if (crl == NULL) { + BIO_printf(bio_err, "Unable to load %s\n", desc); + ERR_print_errors(bio_err); + } + return crl; +} + +X509_REQ *load_csr(const char *file, int format, const char *desc) +{ + X509_REQ *req = NULL; + BIO *in; + + if (format == FORMAT_UNDEF) + format = FORMAT_PEM; + if (desc == NULL) + desc = "CSR"; + in = bio_open_default(file, 'r', format); + if (in == NULL) + goto end; + + if (format == FORMAT_ASN1) + req = d2i_X509_REQ_bio(in, NULL); + else if (format == FORMAT_PEM) + req = PEM_read_bio_X509_REQ(in, NULL, NULL, NULL); + else + print_format_error(format, OPT_FMT_PEMDER); + + end: + if (req == NULL) { + BIO_printf(bio_err, "Unable to load %s\n", desc); + ERR_print_errors(bio_err); + } + BIO_free(in); + return req; +} + +void cleanse(char *str) +{ + if (str != NULL) + OPENSSL_cleanse(str, strlen(str)); +} + +void clear_free(char *str) +{ + if (str != NULL) + OPENSSL_clear_free(str, strlen(str)); +} + +EVP_PKEY *load_key(const char *uri, int format, int may_stdin, + const char *pass, ENGINE *e, const char *desc) +{ + EVP_PKEY *pkey = NULL; + char *allocated_uri = NULL; + + if (desc == NULL) + desc = "private key"; + + if (format == FORMAT_ENGINE) { + uri = allocated_uri = make_engine_uri(e, uri, desc); + } + (void)load_key_certs_crls(uri, format, may_stdin, pass, desc, + &pkey, NULL, NULL, NULL, NULL, NULL, NULL); + + OPENSSL_free(allocated_uri); + return pkey; +} + +EVP_PKEY *load_pubkey(const char *uri, int format, int maybe_stdin, + const char *pass, ENGINE *e, const char *desc) +{ + EVP_PKEY *pkey = NULL; + char *allocated_uri = NULL; + + if (desc == NULL) + desc = "public key"; + + if (format == FORMAT_ENGINE) { + uri = allocated_uri = make_engine_uri(e, uri, desc); + } + (void)load_key_certs_crls(uri, format, maybe_stdin, pass, desc, + NULL, &pkey, NULL, NULL, NULL, NULL, NULL); + + OPENSSL_free(allocated_uri); + return pkey; +} + +EVP_PKEY *load_keyparams_suppress(const char *uri, int format, int maybe_stdin, + const char *keytype, const char *desc, + int suppress_decode_errors) +{ + EVP_PKEY *params = NULL; + + if (desc == NULL) + desc = "key parameters"; + + (void)load_key_certs_crls_suppress(uri, format, maybe_stdin, NULL, desc, + NULL, NULL, ¶ms, NULL, NULL, NULL, + NULL, suppress_decode_errors); + if (params != NULL && keytype != NULL && !EVP_PKEY_is_a(params, keytype)) { + if (!suppress_decode_errors) { + BIO_printf(bio_err, + "Unable to load %s from %s (unexpected parameters type)\n", + desc, uri); + ERR_print_errors(bio_err); + } + EVP_PKEY_free(params); + params = NULL; + } + return params; +} + +EVP_PKEY *load_keyparams(const char *uri, int format, int maybe_stdin, + const char *keytype, const char *desc) +{ + return load_keyparams_suppress(uri, format, maybe_stdin, keytype, desc, 0); +} + +void app_bail_out(char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + BIO_vprintf(bio_err, fmt, args); + va_end(args); + ERR_print_errors(bio_err); + exit(EXIT_FAILURE); +} + +void *app_malloc(size_t sz, const char *what) +{ + void *vp = OPENSSL_malloc(sz); + + if (vp == NULL) + app_bail_out("%s: Could not allocate %zu bytes for %s\n", + opt_getprog(), sz, what); + return vp; +} + +char *next_item(char *opt) /* in list separated by comma and/or space */ +{ + /* advance to separator (comma or whitespace), if any */ + while (*opt != ',' && !isspace(_UC(*opt)) && *opt != '\0') + opt++; + if (*opt != '\0') { + /* terminate current item */ + *opt++ = '\0'; + /* skip over any whitespace after separator */ + while (isspace(_UC(*opt))) + opt++; + } + return *opt == '\0' ? NULL : opt; /* NULL indicates end of input */ +} + +static void warn_cert_msg(const char *uri, X509 *cert, const char *msg) +{ + char *subj = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + + BIO_printf(bio_err, "Warning: certificate from '%s' with subject '%s' %s\n", + uri, subj, msg); + OPENSSL_free(subj); +} + +static void warn_cert(const char *uri, X509 *cert, int warn_EE, + X509_VERIFY_PARAM *vpm) +{ + uint32_t ex_flags = X509_get_extension_flags(cert); + int res = X509_cmp_timeframe(vpm, X509_get0_notBefore(cert), + X509_get0_notAfter(cert)); + + if (res != 0) + warn_cert_msg(uri, cert, res > 0 ? "has expired" : "not yet valid"); + if (warn_EE && (ex_flags & EXFLAG_V1) == 0 && (ex_flags & EXFLAG_CA) == 0) + warn_cert_msg(uri, cert, "is not a CA cert"); +} + +static void warn_certs(const char *uri, STACK_OF(X509) *certs, int warn_EE, + X509_VERIFY_PARAM *vpm) +{ + int i; + + for (i = 0; i < sk_X509_num(certs); i++) + warn_cert(uri, sk_X509_value(certs, i), warn_EE, vpm); +} + +int load_cert_certs(const char *uri, + X509 **pcert, STACK_OF(X509) **pcerts, + int exclude_http, const char *pass, const char *desc, + X509_VERIFY_PARAM *vpm) +{ + int ret = 0; + char *pass_string; + + if (exclude_http && (OPENSSL_strncasecmp(uri, "http://", 7) == 0 + || OPENSSL_strncasecmp(uri, "https://", 8) == 0)) { + BIO_printf(bio_err, "error: HTTP retrieval not allowed for %s\n", desc); + return ret; + } + pass_string = get_passwd(pass, desc); + ret = load_key_certs_crls(uri, FORMAT_UNDEF, 0, pass_string, desc, + NULL, NULL, NULL, + pcert, pcerts, NULL, NULL); + clear_free(pass_string); + + if (ret) { + if (pcert != NULL) + warn_cert(uri, *pcert, 0, vpm); + if (pcerts != NULL) + warn_certs(uri, *pcerts, 1, vpm); + } else { + if (pcerts != NULL) { + sk_X509_pop_free(*pcerts, X509_free); + *pcerts = NULL; + } + } + return ret; +} + +STACK_OF(X509) *load_certs_multifile(char *files, const char *pass, + const char *desc, X509_VERIFY_PARAM *vpm) +{ + STACK_OF(X509) *certs = NULL; + STACK_OF(X509) *result = sk_X509_new_null(); + + if (files == NULL) + goto err; + if (result == NULL) + goto oom; + + while (files != NULL) { + char *next = next_item(files); + + if (!load_cert_certs(files, NULL, &certs, 0, pass, desc, vpm)) + goto err; + if (!X509_add_certs(result, certs, + X509_ADD_FLAG_UP_REF | X509_ADD_FLAG_NO_DUP)) + goto oom; + sk_X509_pop_free(certs, X509_free); + certs = NULL; + files = next; + } + return result; + + oom: + BIO_printf(bio_err, "out of memory\n"); + err: + sk_X509_pop_free(certs, X509_free); + sk_X509_pop_free(result, X509_free); + return NULL; +} + +static X509_STORE *sk_X509_to_store(X509_STORE *store /* may be NULL */, + const STACK_OF(X509) *certs /* may NULL */) +{ + int i; + + if (store == NULL) + store = X509_STORE_new(); + if (store == NULL) + return NULL; + for (i = 0; i < sk_X509_num(certs); i++) { + if (!X509_STORE_add_cert(store, sk_X509_value(certs, i))) { + X509_STORE_free(store); + return NULL; + } + } + return store; +} + +/* + * Create cert store structure with certificates read from given file(s). + * Returns pointer to created X509_STORE on success, NULL on error. + */ +X509_STORE *load_certstore(char *input, const char *pass, const char *desc, + X509_VERIFY_PARAM *vpm) +{ + X509_STORE *store = NULL; + STACK_OF(X509) *certs = NULL; + + while (input != NULL) { + char *next = next_item(input); + int ok; + + if (!load_cert_certs(input, NULL, &certs, 1, pass, desc, vpm)) { + X509_STORE_free(store); + return NULL; + } + ok = (store = sk_X509_to_store(store, certs)) != NULL; + sk_X509_pop_free(certs, X509_free); + certs = NULL; + if (!ok) + return NULL; + input = next; + } + return store; +} + +/* + * Initialize or extend, if *certs != NULL, a certificate stack. + * The caller is responsible for freeing *certs if its value is left not NULL. + */ +int load_certs(const char *uri, int maybe_stdin, STACK_OF(X509) **certs, + const char *pass, const char *desc) +{ + int was_NULL = *certs == NULL; + int ret = load_key_certs_crls(uri, FORMAT_UNDEF, maybe_stdin, + pass, desc, NULL, NULL, + NULL, NULL, certs, NULL, NULL); + + if (!ret && was_NULL) { + sk_X509_pop_free(*certs, X509_free); + *certs = NULL; + } + return ret; +} + +/* + * Initialize or extend, if *crls != NULL, a certificate stack. + * The caller is responsible for freeing *crls if its value is left not NULL. + */ +int load_crls(const char *uri, STACK_OF(X509_CRL) **crls, + const char *pass, const char *desc) +{ + int was_NULL = *crls == NULL; + int ret = load_key_certs_crls(uri, FORMAT_UNDEF, 0, pass, desc, + NULL, NULL, NULL, + NULL, NULL, NULL, crls); + + if (!ret && was_NULL) { + sk_X509_CRL_pop_free(*crls, X509_CRL_free); + *crls = NULL; + } + return ret; +} + +static const char *format2string(int format) +{ + switch(format) { + case FORMAT_PEM: + return "PEM"; + case FORMAT_ASN1: + return "DER"; + } + return NULL; +} + +/* Set type expectation, but clear it if objects of different types expected. */ +#define SET_EXPECT(expect, val) ((expect) = (expect) < 0 ? (val) : ((expect) == (val) ? (val) : 0)) +/* + * Load those types of credentials for which the result pointer is not NULL. + * Reads from stdio if uri is NULL and maybe_stdin is nonzero. + * For non-NULL ppkey, pcert, and pcrl the first suitable value found is loaded. + * If pcerts is non-NULL and *pcerts == NULL then a new cert list is allocated. + * If pcerts is non-NULL then all available certificates are appended to *pcerts + * except any certificate assigned to *pcert. + * If pcrls is non-NULL and *pcrls == NULL then a new list of CRLs is allocated. + * If pcrls is non-NULL then all available CRLs are appended to *pcerts + * except any CRL assigned to *pcrl. + * In any case (also on error) the caller is responsible for freeing all members + * of *pcerts and *pcrls (as far as they are not NULL). + */ +static +int load_key_certs_crls_suppress(const char *uri, int format, int maybe_stdin, + const char *pass, const char *desc, + EVP_PKEY **ppkey, EVP_PKEY **ppubkey, + EVP_PKEY **pparams, + X509 **pcert, STACK_OF(X509) **pcerts, + X509_CRL **pcrl, STACK_OF(X509_CRL) **pcrls, + int suppress_decode_errors) +{ + PW_CB_DATA uidata; + OSSL_STORE_CTX *ctx = NULL; + OSSL_LIB_CTX *libctx = app_get0_libctx(); + const char *propq = app_get0_propq(); + int ncerts = 0; + int ncrls = 0; + const char *failed = + ppkey != NULL ? "key" : ppubkey != NULL ? "public key" : + pparams != NULL ? "params" : pcert != NULL ? "cert" : + pcrl != NULL ? "CRL" : pcerts != NULL ? "certs" : + pcrls != NULL ? "CRLs" : NULL; + int cnt_expectations = 0; + int expect = -1; + const char *input_type; + OSSL_PARAM itp[2]; + const OSSL_PARAM *params = NULL; + + if (ppkey != NULL) { + *ppkey = NULL; + cnt_expectations++; + SET_EXPECT(expect, OSSL_STORE_INFO_PKEY); + } + if (ppubkey != NULL) { + *ppubkey = NULL; + cnt_expectations++; + SET_EXPECT(expect, OSSL_STORE_INFO_PUBKEY); + } + if (pparams != NULL) { + *pparams = NULL; + cnt_expectations++; + SET_EXPECT(expect, OSSL_STORE_INFO_PARAMS); + } + if (pcert != NULL) { + *pcert = NULL; + cnt_expectations++; + SET_EXPECT(expect, OSSL_STORE_INFO_CERT); + } + if (pcerts != NULL) { + if (*pcerts == NULL && (*pcerts = sk_X509_new_null()) == NULL) { + BIO_printf(bio_err, "Out of memory loading"); + goto end; + } + cnt_expectations++; + SET_EXPECT(expect, OSSL_STORE_INFO_CERT); + } + if (pcrl != NULL) { + *pcrl = NULL; + cnt_expectations++; + SET_EXPECT(expect, OSSL_STORE_INFO_CRL); + } + if (pcrls != NULL) { + if (*pcrls == NULL && (*pcrls = sk_X509_CRL_new_null()) == NULL) { + BIO_printf(bio_err, "Out of memory loading"); + goto end; + } + cnt_expectations++; + SET_EXPECT(expect, OSSL_STORE_INFO_CRL); + } + if (cnt_expectations == 0) { + BIO_printf(bio_err, "Internal error: nothing to load from %s\n", + uri != NULL ? uri : "<stdin>"); + return 0; + } + + uidata.password = pass; + uidata.prompt_info = uri; + + if ((input_type = format2string(format)) != NULL) { + itp[0] = OSSL_PARAM_construct_utf8_string(OSSL_STORE_PARAM_INPUT_TYPE, + (char *)input_type, 0); + itp[1] = OSSL_PARAM_construct_end(); + params = itp; + } + + if (uri == NULL) { + BIO *bio; + + if (!maybe_stdin) { + BIO_printf(bio_err, "No filename or uri specified for loading\n"); + goto end; + } + uri = "<stdin>"; + unbuffer(stdin); + bio = BIO_new_fp(stdin, 0); + if (bio != NULL) { + ctx = OSSL_STORE_attach(bio, "file", libctx, propq, + get_ui_method(), &uidata, params, + NULL, NULL); + BIO_free(bio); + } + } else { + ctx = OSSL_STORE_open_ex(uri, libctx, propq, get_ui_method(), &uidata, + params, NULL, NULL); + } + if (ctx == NULL) { + BIO_printf(bio_err, "Could not open file or uri for loading"); + goto end; + } + if (expect > 0 && !OSSL_STORE_expect(ctx, expect)) { + BIO_printf(bio_err, "Internal error trying to load"); + goto end; + } + + failed = NULL; + while (cnt_expectations > 0 && !OSSL_STORE_eof(ctx)) { + OSSL_STORE_INFO *info = OSSL_STORE_load(ctx); + int type, ok = 1; + + /* + * This can happen (for example) if we attempt to load a file with + * multiple different types of things in it - but the thing we just + * tried to load wasn't one of the ones we wanted, e.g. if we're trying + * to load a certificate but the file has both the private key and the + * certificate in it. We just retry until eof. + */ + if (info == NULL) { + continue; + } + + type = OSSL_STORE_INFO_get_type(info); + switch (type) { + case OSSL_STORE_INFO_PKEY: + if (ppkey != NULL && *ppkey == NULL) { + ok = (*ppkey = OSSL_STORE_INFO_get1_PKEY(info)) != NULL; + cnt_expectations -= ok; + } + /* + * An EVP_PKEY with private parts also holds the public parts, + * so if the caller asked for a public key, and we got a private + * key, we can still pass it back. + */ + if (ok && ppubkey != NULL && *ppubkey == NULL) { + ok = ((*ppubkey = OSSL_STORE_INFO_get1_PKEY(info)) != NULL); + cnt_expectations -= ok; + } + break; + case OSSL_STORE_INFO_PUBKEY: + if (ppubkey != NULL && *ppubkey == NULL) { + ok = ((*ppubkey = OSSL_STORE_INFO_get1_PUBKEY(info)) != NULL); + cnt_expectations -= ok; + } + break; + case OSSL_STORE_INFO_PARAMS: + if (pparams != NULL && *pparams == NULL) { + ok = ((*pparams = OSSL_STORE_INFO_get1_PARAMS(info)) != NULL); + cnt_expectations -= ok; + } + break; + case OSSL_STORE_INFO_CERT: + if (pcert != NULL && *pcert == NULL) { + ok = (*pcert = OSSL_STORE_INFO_get1_CERT(info)) != NULL; + cnt_expectations -= ok; + } + else if (pcerts != NULL) + ok = X509_add_cert(*pcerts, + OSSL_STORE_INFO_get1_CERT(info), + X509_ADD_FLAG_DEFAULT); + ncerts += ok; + break; + case OSSL_STORE_INFO_CRL: + if (pcrl != NULL && *pcrl == NULL) { + ok = (*pcrl = OSSL_STORE_INFO_get1_CRL(info)) != NULL; + cnt_expectations -= ok; + } + else if (pcrls != NULL) + ok = sk_X509_CRL_push(*pcrls, OSSL_STORE_INFO_get1_CRL(info)); + ncrls += ok; + break; + default: + /* skip any other type */ + break; + } + OSSL_STORE_INFO_free(info); + if (!ok) { + failed = info == NULL ? NULL : OSSL_STORE_INFO_type_string(type); + BIO_printf(bio_err, "Error reading"); + break; + } + } + + end: + OSSL_STORE_close(ctx); + if (failed == NULL) { + int any = 0; + + if ((ppkey != NULL && *ppkey == NULL) + || (ppubkey != NULL && *ppubkey == NULL)) { + failed = "key"; + } else if (pparams != NULL && *pparams == NULL) { + failed = "params"; + } else if ((pcert != NULL || pcerts != NULL) && ncerts == 0) { + if (pcert == NULL) + any = 1; + failed = "cert"; + } else if ((pcrl != NULL || pcrls != NULL) && ncrls == 0) { + if (pcrl == NULL) + any = 1; + failed = "CRL"; + } + if (!suppress_decode_errors) { + if (failed != NULL) + BIO_printf(bio_err, "Could not read"); + if (any) + BIO_printf(bio_err, " any"); + } + } + if (!suppress_decode_errors && failed != NULL) { + if (desc != NULL && strstr(desc, failed) != NULL) { + BIO_printf(bio_err, " %s", desc); + } else { + BIO_printf(bio_err, " %s", failed); + if (desc != NULL) + BIO_printf(bio_err, " of %s", desc); + } + if (uri != NULL) + BIO_printf(bio_err, " from %s", uri); + BIO_printf(bio_err, "\n"); + ERR_print_errors(bio_err); + } + if (suppress_decode_errors || failed == NULL) + /* clear any spurious errors */ + ERR_clear_error(); + return failed == NULL; +} + +int load_key_certs_crls(const char *uri, int format, int maybe_stdin, + const char *pass, const char *desc, + EVP_PKEY **ppkey, EVP_PKEY **ppubkey, + EVP_PKEY **pparams, + X509 **pcert, STACK_OF(X509) **pcerts, + X509_CRL **pcrl, STACK_OF(X509_CRL) **pcrls) +{ + return load_key_certs_crls_suppress(uri, format, maybe_stdin, pass, desc, + ppkey, ppubkey, pparams, pcert, pcerts, + pcrl, pcrls, 0); +} + +#define X509V3_EXT_UNKNOWN_MASK (0xfL << 16) +/* Return error for unknown extensions */ +#define X509V3_EXT_DEFAULT 0 +/* Print error for unknown extensions */ +#define X509V3_EXT_ERROR_UNKNOWN (1L << 16) +/* ASN1 parse unknown extensions */ +#define X509V3_EXT_PARSE_UNKNOWN (2L << 16) +/* BIO_dump unknown extensions */ +#define X509V3_EXT_DUMP_UNKNOWN (3L << 16) + +#define X509_FLAG_CA (X509_FLAG_NO_ISSUER | X509_FLAG_NO_PUBKEY | \ + X509_FLAG_NO_HEADER | X509_FLAG_NO_VERSION) + +int set_cert_ex(unsigned long *flags, const char *arg) +{ + static const NAME_EX_TBL cert_tbl[] = { + {"compatible", X509_FLAG_COMPAT, 0xffffffffl}, + {"ca_default", X509_FLAG_CA, 0xffffffffl}, + {"no_header", X509_FLAG_NO_HEADER, 0}, + {"no_version", X509_FLAG_NO_VERSION, 0}, + {"no_serial", X509_FLAG_NO_SERIAL, 0}, + {"no_signame", X509_FLAG_NO_SIGNAME, 0}, + {"no_validity", X509_FLAG_NO_VALIDITY, 0}, + {"no_subject", X509_FLAG_NO_SUBJECT, 0}, + {"no_issuer", X509_FLAG_NO_ISSUER, 0}, + {"no_pubkey", X509_FLAG_NO_PUBKEY, 0}, + {"no_extensions", X509_FLAG_NO_EXTENSIONS, 0}, + {"no_sigdump", X509_FLAG_NO_SIGDUMP, 0}, + {"no_aux", X509_FLAG_NO_AUX, 0}, + {"no_attributes", X509_FLAG_NO_ATTRIBUTES, 0}, + {"ext_default", X509V3_EXT_DEFAULT, X509V3_EXT_UNKNOWN_MASK}, + {"ext_error", X509V3_EXT_ERROR_UNKNOWN, X509V3_EXT_UNKNOWN_MASK}, + {"ext_parse", X509V3_EXT_PARSE_UNKNOWN, X509V3_EXT_UNKNOWN_MASK}, + {"ext_dump", X509V3_EXT_DUMP_UNKNOWN, X509V3_EXT_UNKNOWN_MASK}, + {NULL, 0, 0} + }; + return set_multi_opts(flags, arg, cert_tbl); +} + +int set_name_ex(unsigned long *flags, const char *arg) +{ + static const NAME_EX_TBL ex_tbl[] = { + {"esc_2253", ASN1_STRFLGS_ESC_2253, 0}, + {"esc_2254", ASN1_STRFLGS_ESC_2254, 0}, + {"esc_ctrl", ASN1_STRFLGS_ESC_CTRL, 0}, + {"esc_msb", ASN1_STRFLGS_ESC_MSB, 0}, + {"use_quote", ASN1_STRFLGS_ESC_QUOTE, 0}, + {"utf8", ASN1_STRFLGS_UTF8_CONVERT, 0}, + {"ignore_type", ASN1_STRFLGS_IGNORE_TYPE, 0}, + {"show_type", ASN1_STRFLGS_SHOW_TYPE, 0}, + {"dump_all", ASN1_STRFLGS_DUMP_ALL, 0}, + {"dump_nostr", ASN1_STRFLGS_DUMP_UNKNOWN, 0}, + {"dump_der", ASN1_STRFLGS_DUMP_DER, 0}, + {"compat", XN_FLAG_COMPAT, 0xffffffffL}, + {"sep_comma_plus", XN_FLAG_SEP_COMMA_PLUS, XN_FLAG_SEP_MASK}, + {"sep_comma_plus_space", XN_FLAG_SEP_CPLUS_SPC, XN_FLAG_SEP_MASK}, + {"sep_semi_plus_space", XN_FLAG_SEP_SPLUS_SPC, XN_FLAG_SEP_MASK}, + {"sep_multiline", XN_FLAG_SEP_MULTILINE, XN_FLAG_SEP_MASK}, + {"dn_rev", XN_FLAG_DN_REV, 0}, + {"nofname", XN_FLAG_FN_NONE, XN_FLAG_FN_MASK}, + {"sname", XN_FLAG_FN_SN, XN_FLAG_FN_MASK}, + {"lname", XN_FLAG_FN_LN, XN_FLAG_FN_MASK}, + {"align", XN_FLAG_FN_ALIGN, 0}, + {"oid", XN_FLAG_FN_OID, XN_FLAG_FN_MASK}, + {"space_eq", XN_FLAG_SPC_EQ, 0}, + {"dump_unknown", XN_FLAG_DUMP_UNKNOWN_FIELDS, 0}, + {"RFC2253", XN_FLAG_RFC2253, 0xffffffffL}, + {"oneline", XN_FLAG_ONELINE, 0xffffffffL}, + {"multiline", XN_FLAG_MULTILINE, 0xffffffffL}, + {"ca_default", XN_FLAG_MULTILINE, 0xffffffffL}, + {NULL, 0, 0} + }; + if (set_multi_opts(flags, arg, ex_tbl) == 0) + return 0; + if (*flags != XN_FLAG_COMPAT + && (*flags & XN_FLAG_SEP_MASK) == 0) + *flags |= XN_FLAG_SEP_CPLUS_SPC; + return 1; +} + +int set_dateopt(unsigned long *dateopt, const char *arg) +{ + if (OPENSSL_strcasecmp(arg, "rfc_822") == 0) + *dateopt = ASN1_DTFLGS_RFC822; + else if (OPENSSL_strcasecmp(arg, "iso_8601") == 0) + *dateopt = ASN1_DTFLGS_ISO8601; + else + return 0; + return 1; +} + +int set_ext_copy(int *copy_type, const char *arg) +{ + if (OPENSSL_strcasecmp(arg, "none") == 0) + *copy_type = EXT_COPY_NONE; + else if (OPENSSL_strcasecmp(arg, "copy") == 0) + *copy_type = EXT_COPY_ADD; + else if (OPENSSL_strcasecmp(arg, "copyall") == 0) + *copy_type = EXT_COPY_ALL; + else + return 0; + return 1; +} + +int copy_extensions(X509 *x, X509_REQ *req, int copy_type) +{ + STACK_OF(X509_EXTENSION) *exts; + int i, ret = 0; + + if (x == NULL || req == NULL) + return 0; + if (copy_type == EXT_COPY_NONE) + return 1; + exts = X509_REQ_get_extensions(req); + + for (i = 0; i < sk_X509_EXTENSION_num(exts); i++) { + X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i); + ASN1_OBJECT *obj = X509_EXTENSION_get_object(ext); + int idx = X509_get_ext_by_OBJ(x, obj, -1); + + /* Does extension exist in target? */ + if (idx != -1) { + /* If normal copy don't override existing extension */ + if (copy_type == EXT_COPY_ADD) + continue; + /* Delete all extensions of same type */ + do { + X509_EXTENSION_free(X509_delete_ext(x, idx)); + idx = X509_get_ext_by_OBJ(x, obj, -1); + } while (idx != -1); + } + if (!X509_add_ext(x, ext, -1)) + goto end; + } + ret = 1; + + end: + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + return ret; +} + +static int set_multi_opts(unsigned long *flags, const char *arg, + const NAME_EX_TBL * in_tbl) +{ + STACK_OF(CONF_VALUE) *vals; + CONF_VALUE *val; + int i, ret = 1; + if (!arg) + return 0; + vals = X509V3_parse_list(arg); + for (i = 0; i < sk_CONF_VALUE_num(vals); i++) { + val = sk_CONF_VALUE_value(vals, i); + if (!set_table_opts(flags, val->name, in_tbl)) + ret = 0; + } + sk_CONF_VALUE_pop_free(vals, X509V3_conf_free); + return ret; +} + +static int set_table_opts(unsigned long *flags, const char *arg, + const NAME_EX_TBL * in_tbl) +{ + char c; + const NAME_EX_TBL *ptbl; + c = arg[0]; + + if (c == '-') { + c = 0; + arg++; + } else if (c == '+') { + c = 1; + arg++; + } else { + c = 1; + } + + for (ptbl = in_tbl; ptbl->name; ptbl++) { + if (OPENSSL_strcasecmp(arg, ptbl->name) == 0) { + *flags &= ~ptbl->mask; + if (c) + *flags |= ptbl->flag; + else + *flags &= ~ptbl->flag; + return 1; + } + } + return 0; +} + +void print_name(BIO *out, const char *title, const X509_NAME *nm) +{ + char *buf; + char mline = 0; + int indent = 0; + unsigned long lflags = get_nameopt(); + + if (out == NULL) + return; + if (title != NULL) + BIO_puts(out, title); + if ((lflags & XN_FLAG_SEP_MASK) == XN_FLAG_SEP_MULTILINE) { + mline = 1; + indent = 4; + } + if (lflags == XN_FLAG_COMPAT) { + buf = X509_NAME_oneline(nm, 0, 0); + BIO_puts(out, buf); + BIO_puts(out, "\n"); + OPENSSL_free(buf); + } else { + if (mline) + BIO_puts(out, "\n"); + X509_NAME_print_ex(out, nm, indent, lflags); + BIO_puts(out, "\n"); + } +} + +void print_bignum_var(BIO *out, const BIGNUM *in, const char *var, + int len, unsigned char *buffer) +{ + BIO_printf(out, " static unsigned char %s_%d[] = {", var, len); + if (BN_is_zero(in)) { + BIO_printf(out, "\n 0x00"); + } else { + int i, l; + + l = BN_bn2bin(in, buffer); + for (i = 0; i < l; i++) { + BIO_printf(out, (i % 10) == 0 ? "\n " : " "); + if (i < l - 1) + BIO_printf(out, "0x%02X,", buffer[i]); + else + BIO_printf(out, "0x%02X", buffer[i]); + } + } + BIO_printf(out, "\n };\n"); +} + +void print_array(BIO *out, const char* title, int len, const unsigned char* d) +{ + int i; + + BIO_printf(out, "unsigned char %s[%d] = {", title, len); + for (i = 0; i < len; i++) { + if ((i % 10) == 0) + BIO_printf(out, "\n "); + if (i < len - 1) + BIO_printf(out, "0x%02X, ", d[i]); + else + BIO_printf(out, "0x%02X", d[i]); + } + BIO_printf(out, "\n};\n"); +} + +X509_STORE *setup_verify(const char *CAfile, int noCAfile, + const char *CApath, int noCApath, + const char *CAstore, int noCAstore) +{ + X509_STORE *store = X509_STORE_new(); + X509_LOOKUP *lookup; + OSSL_LIB_CTX *libctx = app_get0_libctx(); + const char *propq = app_get0_propq(); + + if (store == NULL) + goto end; + + if (CAfile != NULL || !noCAfile) { + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if (lookup == NULL) + goto end; + if (CAfile != NULL) { + if (X509_LOOKUP_load_file_ex(lookup, CAfile, X509_FILETYPE_PEM, + libctx, propq) <= 0) { + BIO_printf(bio_err, "Error loading file %s\n", CAfile); + goto end; + } + } else { + X509_LOOKUP_load_file_ex(lookup, NULL, X509_FILETYPE_DEFAULT, + libctx, propq); + } + } + + if (CApath != NULL || !noCApath) { + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); + if (lookup == NULL) + goto end; + if (CApath != NULL) { + if (X509_LOOKUP_add_dir(lookup, CApath, X509_FILETYPE_PEM) <= 0) { + BIO_printf(bio_err, "Error loading directory %s\n", CApath); + goto end; + } + } else { + X509_LOOKUP_add_dir(lookup, NULL, X509_FILETYPE_DEFAULT); + } + } + + if (CAstore != NULL || !noCAstore) { + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_store()); + if (lookup == NULL) + goto end; + if (!X509_LOOKUP_add_store_ex(lookup, CAstore, libctx, propq)) { + if (CAstore != NULL) + BIO_printf(bio_err, "Error loading store URI %s\n", CAstore); + goto end; + } + } + + ERR_clear_error(); + return store; + end: + ERR_print_errors(bio_err); + X509_STORE_free(store); + return NULL; +} + +static unsigned long index_serial_hash(const OPENSSL_CSTRING *a) +{ + const char *n; + + n = a[DB_serial]; + while (*n == '0') + n++; + return OPENSSL_LH_strhash(n); +} + +static int index_serial_cmp(const OPENSSL_CSTRING *a, + const OPENSSL_CSTRING *b) +{ + const char *aa, *bb; + + for (aa = a[DB_serial]; *aa == '0'; aa++) ; + for (bb = b[DB_serial]; *bb == '0'; bb++) ; + return strcmp(aa, bb); +} + +static int index_name_qual(char **a) +{ + return (a[0][0] == 'V'); +} + +static unsigned long index_name_hash(const OPENSSL_CSTRING *a) +{ + return OPENSSL_LH_strhash(a[DB_name]); +} + +int index_name_cmp(const OPENSSL_CSTRING *a, const OPENSSL_CSTRING *b) +{ + return strcmp(a[DB_name], b[DB_name]); +} + +static IMPLEMENT_LHASH_HASH_FN(index_serial, OPENSSL_CSTRING) +static IMPLEMENT_LHASH_COMP_FN(index_serial, OPENSSL_CSTRING) +static IMPLEMENT_LHASH_HASH_FN(index_name, OPENSSL_CSTRING) +static IMPLEMENT_LHASH_COMP_FN(index_name, OPENSSL_CSTRING) +#undef BSIZE +#define BSIZE 256 +BIGNUM *load_serial(const char *serialfile, int *exists, int create, + ASN1_INTEGER **retai) +{ + BIO *in = NULL; + BIGNUM *ret = NULL; + char buf[1024]; + ASN1_INTEGER *ai = NULL; + + ai = ASN1_INTEGER_new(); + if (ai == NULL) + goto err; + + in = BIO_new_file(serialfile, "r"); + if (exists != NULL) + *exists = in != NULL; + if (in == NULL) { + if (!create) { + perror(serialfile); + goto err; + } + ERR_clear_error(); + ret = BN_new(); + if (ret == NULL) { + BIO_printf(bio_err, "Out of memory\n"); + } else if (!rand_serial(ret, ai)) { + BIO_printf(bio_err, "Error creating random number to store in %s\n", + serialfile); + BN_free(ret); + ret = NULL; + } + } else { + if (!a2i_ASN1_INTEGER(in, ai, buf, 1024)) { + BIO_printf(bio_err, "Unable to load number from %s\n", + serialfile); + goto err; + } + ret = ASN1_INTEGER_to_BN(ai, NULL); + if (ret == NULL) { + BIO_printf(bio_err, "Error converting number from bin to BIGNUM\n"); + goto err; + } + } + + if (ret != NULL && retai != NULL) { + *retai = ai; + ai = NULL; + } + err: + if (ret == NULL) + ERR_print_errors(bio_err); + BIO_free(in); + ASN1_INTEGER_free(ai); + return ret; +} + +int save_serial(const char *serialfile, const char *suffix, const BIGNUM *serial, + ASN1_INTEGER **retai) +{ + char buf[1][BSIZE]; + BIO *out = NULL; + int ret = 0; + ASN1_INTEGER *ai = NULL; + int j; + + if (suffix == NULL) + j = strlen(serialfile); + else + j = strlen(serialfile) + strlen(suffix) + 1; + if (j >= BSIZE) { + BIO_printf(bio_err, "File name too long\n"); + goto err; + } + + if (suffix == NULL) + OPENSSL_strlcpy(buf[0], serialfile, BSIZE); + else { +#ifndef OPENSSL_SYS_VMS + j = BIO_snprintf(buf[0], sizeof(buf[0]), "%s.%s", serialfile, suffix); +#else + j = BIO_snprintf(buf[0], sizeof(buf[0]), "%s-%s", serialfile, suffix); +#endif + } + out = BIO_new_file(buf[0], "w"); + if (out == NULL) { + goto err; + } + + if ((ai = BN_to_ASN1_INTEGER(serial, NULL)) == NULL) { + BIO_printf(bio_err, "error converting serial to ASN.1 format\n"); + goto err; + } + i2a_ASN1_INTEGER(out, ai); + BIO_puts(out, "\n"); + ret = 1; + if (retai) { + *retai = ai; + ai = NULL; + } + err: + if (!ret) + ERR_print_errors(bio_err); + BIO_free_all(out); + ASN1_INTEGER_free(ai); + return ret; +} + +int rotate_serial(const char *serialfile, const char *new_suffix, + const char *old_suffix) +{ + char buf[2][BSIZE]; + int i, j; + + i = strlen(serialfile) + strlen(old_suffix); + j = strlen(serialfile) + strlen(new_suffix); + if (i > j) + j = i; + if (j + 1 >= BSIZE) { + BIO_printf(bio_err, "File name too long\n"); + goto err; + } +#ifndef OPENSSL_SYS_VMS + j = BIO_snprintf(buf[0], sizeof(buf[0]), "%s.%s", serialfile, new_suffix); + j = BIO_snprintf(buf[1], sizeof(buf[1]), "%s.%s", serialfile, old_suffix); +#else + j = BIO_snprintf(buf[0], sizeof(buf[0]), "%s-%s", serialfile, new_suffix); + j = BIO_snprintf(buf[1], sizeof(buf[1]), "%s-%s", serialfile, old_suffix); +#endif + if (rename(serialfile, buf[1]) < 0 && errno != ENOENT +#ifdef ENOTDIR + && errno != ENOTDIR +#endif + ) { + BIO_printf(bio_err, + "Unable to rename %s to %s\n", serialfile, buf[1]); + perror("reason"); + goto err; + } + if (rename(buf[0], serialfile) < 0) { + BIO_printf(bio_err, + "Unable to rename %s to %s\n", buf[0], serialfile); + perror("reason"); + rename(buf[1], serialfile); + goto err; + } + return 1; + err: + ERR_print_errors(bio_err); + return 0; +} + +int rand_serial(BIGNUM *b, ASN1_INTEGER *ai) +{ + BIGNUM *btmp; + int ret = 0; + + btmp = b == NULL ? BN_new() : b; + if (btmp == NULL) + return 0; + + if (!BN_rand(btmp, SERIAL_RAND_BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) + goto error; + if (ai && !BN_to_ASN1_INTEGER(btmp, ai)) + goto error; + + ret = 1; + + error: + + if (btmp != b) + BN_free(btmp); + + return ret; +} + +CA_DB *load_index(const char *dbfile, DB_ATTR *db_attr) +{ + CA_DB *retdb = NULL; + TXT_DB *tmpdb = NULL; + BIO *in; + CONF *dbattr_conf = NULL; + char buf[BSIZE]; +#ifndef OPENSSL_NO_POSIX_IO + FILE *dbfp; + struct stat dbst; +#endif + + in = BIO_new_file(dbfile, "r"); + if (in == NULL) + goto err; + +#ifndef OPENSSL_NO_POSIX_IO + BIO_get_fp(in, &dbfp); + if (fstat(fileno(dbfp), &dbst) == -1) { + ERR_raise_data(ERR_LIB_SYS, errno, + "calling fstat(%s)", dbfile); + goto err; + } +#endif + + if ((tmpdb = TXT_DB_read(in, DB_NUMBER)) == NULL) + goto err; + +#ifndef OPENSSL_SYS_VMS + BIO_snprintf(buf, sizeof(buf), "%s.attr", dbfile); +#else + BIO_snprintf(buf, sizeof(buf), "%s-attr", dbfile); +#endif + dbattr_conf = app_load_config_quiet(buf); + + retdb = app_malloc(sizeof(*retdb), "new DB"); + retdb->db = tmpdb; + tmpdb = NULL; + if (db_attr) + retdb->attributes = *db_attr; + else { + retdb->attributes.unique_subject = 1; + } + + if (dbattr_conf) { + char *p = NCONF_get_string(dbattr_conf, NULL, "unique_subject"); + if (p) { + retdb->attributes.unique_subject = parse_yesno(p, 1); + } else { + ERR_clear_error(); + } + + } + + retdb->dbfname = OPENSSL_strdup(dbfile); +#ifndef OPENSSL_NO_POSIX_IO + retdb->dbst = dbst; +#endif + + err: + ERR_print_errors(bio_err); + NCONF_free(dbattr_conf); + TXT_DB_free(tmpdb); + BIO_free_all(in); + return retdb; +} + +/* + * Returns > 0 on success, <= 0 on error + */ +int index_index(CA_DB *db) +{ + if (!TXT_DB_create_index(db->db, DB_serial, NULL, + LHASH_HASH_FN(index_serial), + LHASH_COMP_FN(index_serial))) { + BIO_printf(bio_err, + "Error creating serial number index:(%ld,%ld,%ld)\n", + db->db->error, db->db->arg1, db->db->arg2); + goto err; + } + + if (db->attributes.unique_subject + && !TXT_DB_create_index(db->db, DB_name, index_name_qual, + LHASH_HASH_FN(index_name), + LHASH_COMP_FN(index_name))) { + BIO_printf(bio_err, "Error creating name index:(%ld,%ld,%ld)\n", + db->db->error, db->db->arg1, db->db->arg2); + goto err; + } + return 1; + err: + ERR_print_errors(bio_err); + return 0; +} + +int save_index(const char *dbfile, const char *suffix, CA_DB *db) +{ + char buf[3][BSIZE]; + BIO *out; + int j; + + j = strlen(dbfile) + strlen(suffix); + if (j + 6 >= BSIZE) { + BIO_printf(bio_err, "File name too long\n"); + goto err; + } +#ifndef OPENSSL_SYS_VMS + j = BIO_snprintf(buf[2], sizeof(buf[2]), "%s.attr", dbfile); + j = BIO_snprintf(buf[1], sizeof(buf[1]), "%s.attr.%s", dbfile, suffix); + j = BIO_snprintf(buf[0], sizeof(buf[0]), "%s.%s", dbfile, suffix); +#else + j = BIO_snprintf(buf[2], sizeof(buf[2]), "%s-attr", dbfile); + j = BIO_snprintf(buf[1], sizeof(buf[1]), "%s-attr-%s", dbfile, suffix); + j = BIO_snprintf(buf[0], sizeof(buf[0]), "%s-%s", dbfile, suffix); +#endif + out = BIO_new_file(buf[0], "w"); + if (out == NULL) { + perror(dbfile); + BIO_printf(bio_err, "Unable to open '%s'\n", dbfile); + goto err; + } + j = TXT_DB_write(out, db->db); + BIO_free(out); + if (j <= 0) + goto err; + + out = BIO_new_file(buf[1], "w"); + if (out == NULL) { + perror(buf[2]); + BIO_printf(bio_err, "Unable to open '%s'\n", buf[2]); + goto err; + } + BIO_printf(out, "unique_subject = %s\n", + db->attributes.unique_subject ? "yes" : "no"); + BIO_free(out); + + return 1; + err: + ERR_print_errors(bio_err); + return 0; +} + +int rotate_index(const char *dbfile, const char *new_suffix, + const char *old_suffix) +{ + char buf[5][BSIZE]; + int i, j; + + i = strlen(dbfile) + strlen(old_suffix); + j = strlen(dbfile) + strlen(new_suffix); + if (i > j) + j = i; + if (j + 6 >= BSIZE) { + BIO_printf(bio_err, "File name too long\n"); + goto err; + } +#ifndef OPENSSL_SYS_VMS + j = BIO_snprintf(buf[4], sizeof(buf[4]), "%s.attr", dbfile); + j = BIO_snprintf(buf[3], sizeof(buf[3]), "%s.attr.%s", dbfile, old_suffix); + j = BIO_snprintf(buf[2], sizeof(buf[2]), "%s.attr.%s", dbfile, new_suffix); + j = BIO_snprintf(buf[1], sizeof(buf[1]), "%s.%s", dbfile, old_suffix); + j = BIO_snprintf(buf[0], sizeof(buf[0]), "%s.%s", dbfile, new_suffix); +#else + j = BIO_snprintf(buf[4], sizeof(buf[4]), "%s-attr", dbfile); + j = BIO_snprintf(buf[3], sizeof(buf[3]), "%s-attr-%s", dbfile, old_suffix); + j = BIO_snprintf(buf[2], sizeof(buf[2]), "%s-attr-%s", dbfile, new_suffix); + j = BIO_snprintf(buf[1], sizeof(buf[1]), "%s-%s", dbfile, old_suffix); + j = BIO_snprintf(buf[0], sizeof(buf[0]), "%s-%s", dbfile, new_suffix); +#endif + if (rename(dbfile, buf[1]) < 0 && errno != ENOENT +#ifdef ENOTDIR + && errno != ENOTDIR +#endif + ) { + BIO_printf(bio_err, "Unable to rename %s to %s\n", dbfile, buf[1]); + perror("reason"); + goto err; + } + if (rename(buf[0], dbfile) < 0) { + BIO_printf(bio_err, "Unable to rename %s to %s\n", buf[0], dbfile); + perror("reason"); + rename(buf[1], dbfile); + goto err; + } + if (rename(buf[4], buf[3]) < 0 && errno != ENOENT +#ifdef ENOTDIR + && errno != ENOTDIR +#endif + ) { + BIO_printf(bio_err, "Unable to rename %s to %s\n", buf[4], buf[3]); + perror("reason"); + rename(dbfile, buf[0]); + rename(buf[1], dbfile); + goto err; + } + if (rename(buf[2], buf[4]) < 0) { + BIO_printf(bio_err, "Unable to rename %s to %s\n", buf[2], buf[4]); + perror("reason"); + rename(buf[3], buf[4]); + rename(dbfile, buf[0]); + rename(buf[1], dbfile); + goto err; + } + return 1; + err: + ERR_print_errors(bio_err); + return 0; +} + +void free_index(CA_DB *db) +{ + if (db) { + TXT_DB_free(db->db); + OPENSSL_free(db->dbfname); + OPENSSL_free(db); + } +} + +int parse_yesno(const char *str, int def) +{ + if (str) { + switch (*str) { + case 'f': /* false */ + case 'F': /* FALSE */ + case 'n': /* no */ + case 'N': /* NO */ + case '0': /* 0 */ + return 0; + case 't': /* true */ + case 'T': /* TRUE */ + case 'y': /* yes */ + case 'Y': /* YES */ + case '1': /* 1 */ + return 1; + } + } + return def; +} + +/* + * name is expected to be in the format /type0=value0/type1=value1/type2=... + * where + can be used instead of / to form multi-valued RDNs if canmulti + * and characters may be escaped by \ + */ +X509_NAME *parse_name(const char *cp, int chtype, int canmulti, + const char *desc) +{ + int nextismulti = 0; + char *work; + X509_NAME *n; + + if (*cp++ != '/') { + BIO_printf(bio_err, + "%s: %s name is expected to be in the format " + "/type0=value0/type1=value1/type2=... where characters may " + "be escaped by \\. This name is not in that format: '%s'\n", + opt_getprog(), desc, --cp); + return NULL; + } + + n = X509_NAME_new(); + if (n == NULL) { + BIO_printf(bio_err, "%s: Out of memory\n", opt_getprog()); + return NULL; + } + work = OPENSSL_strdup(cp); + if (work == NULL) { + BIO_printf(bio_err, "%s: Error copying %s name input\n", + opt_getprog(), desc); + goto err; + } + + while (*cp != '\0') { + char *bp = work; + char *typestr = bp; + unsigned char *valstr; + int nid; + int ismulti = nextismulti; + nextismulti = 0; + + /* Collect the type */ + while (*cp != '\0' && *cp != '=') + *bp++ = *cp++; + *bp++ = '\0'; + if (*cp == '\0') { + BIO_printf(bio_err, + "%s: Missing '=' after RDN type string '%s' in %s name string\n", + opt_getprog(), typestr, desc); + goto err; + } + ++cp; + + /* Collect the value. */ + valstr = (unsigned char *)bp; + for (; *cp != '\0' && *cp != '/'; *bp++ = *cp++) { + /* unescaped '+' symbol string signals further member of multiRDN */ + if (canmulti && *cp == '+') { + nextismulti = 1; + break; + } + if (*cp == '\\' && *++cp == '\0') { + BIO_printf(bio_err, + "%s: Escape character at end of %s name string\n", + opt_getprog(), desc); + goto err; + } + } + *bp++ = '\0'; + + /* If not at EOS (must be + or /), move forward. */ + if (*cp != '\0') + ++cp; + + /* Parse */ + nid = OBJ_txt2nid(typestr); + if (nid == NID_undef) { + BIO_printf(bio_err, + "%s warning: Skipping unknown %s name attribute \"%s\"\n", + opt_getprog(), desc, typestr); + if (ismulti) + BIO_printf(bio_err, + "%s hint: a '+' in a value string needs be escaped using '\\' else a new member of a multi-valued RDN is expected\n", + opt_getprog()); + continue; + } + if (*valstr == '\0') { + BIO_printf(bio_err, + "%s warning: No value provided for %s name attribute \"%s\", skipped\n", + opt_getprog(), desc, typestr); + continue; + } + if (!X509_NAME_add_entry_by_NID(n, nid, chtype, + valstr, strlen((char *)valstr), + -1, ismulti ? -1 : 0)) { + ERR_print_errors(bio_err); + BIO_printf(bio_err, + "%s: Error adding %s name attribute \"/%s=%s\"\n", + opt_getprog(), desc, typestr ,valstr); + goto err; + } + } + + OPENSSL_free(work); + return n; + + err: + X509_NAME_free(n); + OPENSSL_free(work); + return NULL; +} + +/* + * Read whole contents of a BIO into an allocated memory buffer and return + * it. + */ + +int bio_to_mem(unsigned char **out, int maxlen, BIO *in) +{ + BIO *mem; + int len, ret; + unsigned char tbuf[1024]; + + mem = BIO_new(BIO_s_mem()); + if (mem == NULL) + return -1; + for (;;) { + if ((maxlen != -1) && maxlen < 1024) + len = maxlen; + else + len = 1024; + len = BIO_read(in, tbuf, len); + if (len < 0) { + BIO_free(mem); + return -1; + } + if (len == 0) + break; + if (BIO_write(mem, tbuf, len) != len) { + BIO_free(mem); + return -1; + } + if (maxlen != -1) + maxlen -= len; + + if (maxlen == 0) + break; + } + ret = BIO_get_mem_data(mem, (char **)out); + BIO_set_flags(mem, BIO_FLAGS_MEM_RDONLY); + BIO_free(mem); + return ret; +} + +int pkey_ctrl_string(EVP_PKEY_CTX *ctx, const char *value) +{ + int rv = 0; + char *stmp, *vtmp = NULL; + + stmp = OPENSSL_strdup(value); + if (stmp == NULL) + return -1; + vtmp = strchr(stmp, ':'); + if (vtmp == NULL) + goto err; + + *vtmp = 0; + vtmp++; + rv = EVP_PKEY_CTX_ctrl_str(ctx, stmp, vtmp); + + err: + OPENSSL_free(stmp); + return rv; +} + +static void nodes_print(const char *name, STACK_OF(X509_POLICY_NODE) *nodes) +{ + X509_POLICY_NODE *node; + int i; + + BIO_printf(bio_err, "%s Policies:", name); + if (nodes) { + BIO_puts(bio_err, "\n"); + for (i = 0; i < sk_X509_POLICY_NODE_num(nodes); i++) { + node = sk_X509_POLICY_NODE_value(nodes, i); + X509_POLICY_NODE_print(bio_err, node, 2); + } + } else { + BIO_puts(bio_err, " <empty>\n"); + } +} + +void policies_print(X509_STORE_CTX *ctx) +{ + X509_POLICY_TREE *tree; + int explicit_policy; + tree = X509_STORE_CTX_get0_policy_tree(ctx); + explicit_policy = X509_STORE_CTX_get_explicit_policy(ctx); + + BIO_printf(bio_err, "Require explicit Policy: %s\n", + explicit_policy ? "True" : "False"); + + nodes_print("Authority", X509_policy_tree_get0_policies(tree)); + nodes_print("User", X509_policy_tree_get0_user_policies(tree)); +} + +/*- + * next_protos_parse parses a comma separated list of strings into a string + * in a format suitable for passing to SSL_CTX_set_next_protos_advertised. + * outlen: (output) set to the length of the resulting buffer on success. + * err: (maybe NULL) on failure, an error message line is written to this BIO. + * in: a NUL terminated string like "abc,def,ghi" + * + * returns: a malloc'd buffer or NULL on failure. + */ +unsigned char *next_protos_parse(size_t *outlen, const char *in) +{ + size_t len; + unsigned char *out; + size_t i, start = 0; + size_t skipped = 0; + + len = strlen(in); + if (len == 0 || len >= 65535) + return NULL; + + out = app_malloc(len + 1, "NPN buffer"); + for (i = 0; i <= len; ++i) { + if (i == len || in[i] == ',') { + /* + * Zero-length ALPN elements are invalid on the wire, we could be + * strict and reject the entire string, but just ignoring extra + * commas seems harmless and more friendly. + * + * Every comma we skip in this way puts the input buffer another + * byte ahead of the output buffer, so all stores into the output + * buffer need to be decremented by the number commas skipped. + */ + if (i == start) { + ++start; + ++skipped; + continue; + } + if (i - start > 255) { + OPENSSL_free(out); + return NULL; + } + out[start-skipped] = (unsigned char)(i - start); + start = i + 1; + } else { + out[i + 1 - skipped] = in[i]; + } + } + + if (len <= skipped) { + OPENSSL_free(out); + return NULL; + } + + *outlen = len + 1 - skipped; + return out; +} + +void print_cert_checks(BIO *bio, X509 *x, + const char *checkhost, + const char *checkemail, const char *checkip) +{ + if (x == NULL) + return; + if (checkhost) { + BIO_printf(bio, "Hostname %s does%s match certificate\n", + checkhost, + X509_check_host(x, checkhost, 0, 0, NULL) == 1 + ? "" : " NOT"); + } + + if (checkemail) { + BIO_printf(bio, "Email %s does%s match certificate\n", + checkemail, X509_check_email(x, checkemail, 0, 0) + ? "" : " NOT"); + } + + if (checkip) { + BIO_printf(bio, "IP %s does%s match certificate\n", + checkip, X509_check_ip_asc(x, checkip, 0) ? "" : " NOT"); + } +} + +static int do_pkey_ctx_init(EVP_PKEY_CTX *pkctx, STACK_OF(OPENSSL_STRING) *opts) +{ + int i; + + if (opts == NULL) + return 1; + + for (i = 0; i < sk_OPENSSL_STRING_num(opts); i++) { + char *opt = sk_OPENSSL_STRING_value(opts, i); + if (pkey_ctrl_string(pkctx, opt) <= 0) { + BIO_printf(bio_err, "parameter error \"%s\"\n", opt); + ERR_print_errors(bio_err); + return 0; + } + } + + return 1; +} + +static int do_x509_init(X509 *x, STACK_OF(OPENSSL_STRING) *opts) +{ + int i; + + if (opts == NULL) + return 1; + + for (i = 0; i < sk_OPENSSL_STRING_num(opts); i++) { + char *opt = sk_OPENSSL_STRING_value(opts, i); + if (x509_ctrl_string(x, opt) <= 0) { + BIO_printf(bio_err, "parameter error \"%s\"\n", opt); + ERR_print_errors(bio_err); + return 0; + } + } + + return 1; +} + +static int do_x509_req_init(X509_REQ *x, STACK_OF(OPENSSL_STRING) *opts) +{ + int i; + + if (opts == NULL) + return 1; + + for (i = 0; i < sk_OPENSSL_STRING_num(opts); i++) { + char *opt = sk_OPENSSL_STRING_value(opts, i); + if (x509_req_ctrl_string(x, opt) <= 0) { + BIO_printf(bio_err, "parameter error \"%s\"\n", opt); + ERR_print_errors(bio_err); + return 0; + } + } + + return 1; +} + +static int do_sign_init(EVP_MD_CTX *ctx, EVP_PKEY *pkey, + const char *md, STACK_OF(OPENSSL_STRING) *sigopts) +{ + EVP_PKEY_CTX *pkctx = NULL; + char def_md[80]; + + if (ctx == NULL) + return 0; + /* + * EVP_PKEY_get_default_digest_name() returns 2 if the digest is mandatory + * for this algorithm. + */ + if (EVP_PKEY_get_default_digest_name(pkey, def_md, sizeof(def_md)) == 2 + && strcmp(def_md, "UNDEF") == 0) { + /* The signing algorithm requires there to be no digest */ + md = NULL; + } + + return EVP_DigestSignInit_ex(ctx, &pkctx, md, app_get0_libctx(), + app_get0_propq(), pkey, NULL) + && do_pkey_ctx_init(pkctx, sigopts); +} + +static int adapt_keyid_ext(X509 *cert, X509V3_CTX *ext_ctx, + const char *name, const char *value, int add_default) +{ + const STACK_OF(X509_EXTENSION) *exts = X509_get0_extensions(cert); + X509_EXTENSION *new_ext = X509V3_EXT_nconf(NULL, ext_ctx, name, value); + int idx, rv = 0; + + if (new_ext == NULL) + return rv; + + idx = X509v3_get_ext_by_OBJ(exts, X509_EXTENSION_get_object(new_ext), -1); + if (idx >= 0) { + X509_EXTENSION *found_ext = X509v3_get_ext(exts, idx); + ASN1_OCTET_STRING *data = X509_EXTENSION_get_data(found_ext); + int disabled = ASN1_STRING_length(data) <= 2; /* config said "none" */ + + if (disabled) { + X509_delete_ext(cert, idx); + X509_EXTENSION_free(found_ext); + } /* else keep existing key identifier, which might be outdated */ + rv = 1; + } else { + rv = !add_default || X509_add_ext(cert, new_ext, -1); + } + X509_EXTENSION_free(new_ext); + return rv; +} + +/* Ensure RFC 5280 compliance, adapt keyIDs as needed, and sign the cert info */ +int do_X509_sign(X509 *cert, EVP_PKEY *pkey, const char *md, + STACK_OF(OPENSSL_STRING) *sigopts, X509V3_CTX *ext_ctx) +{ + const STACK_OF(X509_EXTENSION) *exts = X509_get0_extensions(cert); + EVP_MD_CTX *mctx = EVP_MD_CTX_new(); + int self_sign; + int rv = 0; + + if (sk_X509_EXTENSION_num(exts /* may be NULL */) > 0) { + /* Prevent X509_V_ERR_EXTENSIONS_REQUIRE_VERSION_3 */ + if (!X509_set_version(cert, X509_VERSION_3)) + goto end; + + /* + * Add default SKID before such that default AKID can make use of it + * in case the certificate is self-signed + */ + /* Prevent X509_V_ERR_MISSING_SUBJECT_KEY_IDENTIFIER */ + if (!adapt_keyid_ext(cert, ext_ctx, "subjectKeyIdentifier", "hash", 1)) + goto end; + /* Prevent X509_V_ERR_MISSING_AUTHORITY_KEY_IDENTIFIER */ + ERR_set_mark(); + self_sign = X509_check_private_key(cert, pkey); + ERR_pop_to_mark(); + if (!adapt_keyid_ext(cert, ext_ctx, "authorityKeyIdentifier", + "keyid, issuer", !self_sign)) + goto end; + } + + if (mctx != NULL && do_sign_init(mctx, pkey, md, sigopts) > 0) + rv = (X509_sign_ctx(cert, mctx) > 0); + end: + EVP_MD_CTX_free(mctx); + return rv; +} + +/* Sign the certificate request info */ +int do_X509_REQ_sign(X509_REQ *x, EVP_PKEY *pkey, const char *md, + STACK_OF(OPENSSL_STRING) *sigopts) +{ + int rv = 0; + EVP_MD_CTX *mctx = EVP_MD_CTX_new(); + + if (do_sign_init(mctx, pkey, md, sigopts) > 0) + rv = (X509_REQ_sign_ctx(x, mctx) > 0); + EVP_MD_CTX_free(mctx); + return rv; +} + +/* Sign the CRL info */ +int do_X509_CRL_sign(X509_CRL *x, EVP_PKEY *pkey, const char *md, + STACK_OF(OPENSSL_STRING) *sigopts) +{ + int rv = 0; + EVP_MD_CTX *mctx = EVP_MD_CTX_new(); + + if (do_sign_init(mctx, pkey, md, sigopts) > 0) + rv = (X509_CRL_sign_ctx(x, mctx) > 0); + EVP_MD_CTX_free(mctx); + return rv; +} + +/* + * do_X509_verify returns 1 if the signature is valid, + * 0 if the signature check fails, or -1 if error occurs. + */ +int do_X509_verify(X509 *x, EVP_PKEY *pkey, STACK_OF(OPENSSL_STRING) *vfyopts) +{ + int rv = 0; + + if (do_x509_init(x, vfyopts) > 0) + rv = X509_verify(x, pkey); + else + rv = -1; + return rv; +} + +/* + * do_X509_REQ_verify returns 1 if the signature is valid, + * 0 if the signature check fails, or -1 if error occurs. + */ +int do_X509_REQ_verify(X509_REQ *x, EVP_PKEY *pkey, + STACK_OF(OPENSSL_STRING) *vfyopts) +{ + int rv = 0; + + if (do_x509_req_init(x, vfyopts) > 0) + rv = X509_REQ_verify_ex(x, pkey, + app_get0_libctx(), app_get0_propq()); + else + rv = -1; + return rv; +} + +/* Get first http URL from a DIST_POINT structure */ + +static const char *get_dp_url(DIST_POINT *dp) +{ + GENERAL_NAMES *gens; + GENERAL_NAME *gen; + int i, gtype; + ASN1_STRING *uri; + if (!dp->distpoint || dp->distpoint->type != 0) + return NULL; + gens = dp->distpoint->name.fullname; + for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) { + gen = sk_GENERAL_NAME_value(gens, i); + uri = GENERAL_NAME_get0_value(gen, >ype); + if (gtype == GEN_URI && ASN1_STRING_length(uri) > 6) { + const char *uptr = (const char *)ASN1_STRING_get0_data(uri); + + if (IS_HTTP(uptr)) /* can/should not use HTTPS here */ + return uptr; + } + } + return NULL; +} + +/* + * Look through a CRLDP structure and attempt to find an http URL to + * downloads a CRL from. + */ + +static X509_CRL *load_crl_crldp(STACK_OF(DIST_POINT) *crldp) +{ + int i; + const char *urlptr = NULL; + for (i = 0; i < sk_DIST_POINT_num(crldp); i++) { + DIST_POINT *dp = sk_DIST_POINT_value(crldp, i); + urlptr = get_dp_url(dp); + if (urlptr != NULL) + return load_crl(urlptr, FORMAT_UNDEF, 0, "CRL via CDP"); + } + return NULL; +} + +/* + * Example of downloading CRLs from CRLDP: + * not usable for real world as it always downloads and doesn't cache anything. + */ + +static STACK_OF(X509_CRL) *crls_http_cb(const X509_STORE_CTX *ctx, + const X509_NAME *nm) +{ + X509 *x; + STACK_OF(X509_CRL) *crls = NULL; + X509_CRL *crl; + STACK_OF(DIST_POINT) *crldp; + + crls = sk_X509_CRL_new_null(); + if (!crls) + return NULL; + x = X509_STORE_CTX_get_current_cert(ctx); + crldp = X509_get_ext_d2i(x, NID_crl_distribution_points, NULL, NULL); + crl = load_crl_crldp(crldp); + sk_DIST_POINT_pop_free(crldp, DIST_POINT_free); + if (!crl) { + sk_X509_CRL_free(crls); + return NULL; + } + sk_X509_CRL_push(crls, crl); + /* Try to download delta CRL */ + crldp = X509_get_ext_d2i(x, NID_freshest_crl, NULL, NULL); + crl = load_crl_crldp(crldp); + sk_DIST_POINT_pop_free(crldp, DIST_POINT_free); + if (crl) + sk_X509_CRL_push(crls, crl); + return crls; +} + +void store_setup_crl_download(X509_STORE *st) +{ + X509_STORE_set_lookup_crls_cb(st, crls_http_cb); +} + +#ifndef OPENSSL_NO_SOCK +static const char *tls_error_hint(void) +{ + unsigned long err = ERR_peek_error(); + + if (ERR_GET_LIB(err) != ERR_LIB_SSL) + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) != ERR_LIB_SSL) + return NULL; + + switch (ERR_GET_REASON(err)) { + case SSL_R_WRONG_VERSION_NUMBER: + return "The server does not support (a suitable version of) TLS"; + case SSL_R_UNKNOWN_PROTOCOL: + return "The server does not support HTTPS"; + case SSL_R_CERTIFICATE_VERIFY_FAILED: + return "Cannot authenticate server via its TLS certificate, likely due to mismatch with our trusted TLS certs or missing revocation status"; + case SSL_AD_REASON_OFFSET + TLS1_AD_UNKNOWN_CA: + return "Server did not accept our TLS certificate, likely due to mismatch with server's trust anchor or missing revocation status"; + case SSL_AD_REASON_OFFSET + SSL3_AD_HANDSHAKE_FAILURE: + return "TLS handshake failure. Possibly the server requires our TLS certificate but did not receive it"; + default: /* no error or no hint available for error */ + return NULL; + } +} + +/* HTTP callback function that supports TLS connection also via HTTPS proxy */ +BIO *app_http_tls_cb(BIO *bio, void *arg, int connect, int detail) +{ + APP_HTTP_TLS_INFO *info = (APP_HTTP_TLS_INFO *)arg; + SSL_CTX *ssl_ctx = info->ssl_ctx; + + if (ssl_ctx == NULL) /* not using TLS */ + return bio; + if (connect) { + SSL *ssl; + BIO *sbio = NULL; + X509_STORE *ts = SSL_CTX_get_cert_store(ssl_ctx); + X509_VERIFY_PARAM *vpm = X509_STORE_get0_param(ts); + const char *host = vpm == NULL ? NULL : + X509_VERIFY_PARAM_get0_host(vpm, 0 /* first hostname */); + + /* adapt after fixing callback design flaw, see #17088 */ + if ((info->use_proxy + && !OSSL_HTTP_proxy_connect(bio, info->server, info->port, + NULL, NULL, /* no proxy credentials */ + info->timeout, bio_err, opt_getprog())) + || (sbio = BIO_new(BIO_f_ssl())) == NULL) { + return NULL; + } + if (ssl_ctx == NULL || (ssl = SSL_new(ssl_ctx)) == NULL) { + BIO_free(sbio); + return NULL; + } + + if (vpm != NULL) + SSL_set_tlsext_host_name(ssl, host /* may be NULL */); + + SSL_set_connect_state(ssl); + BIO_set_ssl(sbio, ssl, BIO_CLOSE); + + bio = BIO_push(sbio, bio); + } + if (!connect) { + const char *hint; + BIO *cbio; + + if (!detail) { /* disconnecting after error */ + hint = tls_error_hint(); + if (hint != NULL) + ERR_add_error_data(2, " : ", hint); + } + if (ssl_ctx != NULL) { + (void)ERR_set_mark(); + BIO_ssl_shutdown(bio); + cbio = BIO_pop(bio); /* connect+HTTP BIO */ + BIO_free(bio); /* SSL BIO */ + (void)ERR_pop_to_mark(); /* hide SSL_R_READ_BIO_NOT_SET etc. */ + bio = cbio; + } + } + return bio; +} + +void APP_HTTP_TLS_INFO_free(APP_HTTP_TLS_INFO *info) +{ + if (info != NULL) { + SSL_CTX_free(info->ssl_ctx); + OPENSSL_free(info); + } +} + +ASN1_VALUE *app_http_get_asn1(const char *url, const char *proxy, + const char *no_proxy, SSL_CTX *ssl_ctx, + const STACK_OF(CONF_VALUE) *headers, + long timeout, const char *expected_content_type, + const ASN1_ITEM *it) +{ + APP_HTTP_TLS_INFO info; + char *server; + char *port; + int use_ssl; + BIO *mem; + ASN1_VALUE *resp = NULL; + + if (url == NULL || it == NULL) { + ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); + return NULL; + } + + if (!OSSL_HTTP_parse_url(url, &use_ssl, NULL /* userinfo */, &server, &port, + NULL /* port_num, */, NULL, NULL, NULL)) + return NULL; + if (use_ssl && ssl_ctx == NULL) { + ERR_raise_data(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER, + "missing SSL_CTX"); + goto end; + } + if (!use_ssl && ssl_ctx != NULL) { + ERR_raise_data(ERR_LIB_HTTP, ERR_R_PASSED_INVALID_ARGUMENT, + "SSL_CTX given but use_ssl == 0"); + goto end; + } + + info.server = server; + info.port = port; + info.use_proxy = /* workaround for callback design flaw, see #17088 */ + OSSL_HTTP_adapt_proxy(proxy, no_proxy, server, use_ssl) != NULL; + info.timeout = timeout; + info.ssl_ctx = ssl_ctx; + mem = OSSL_HTTP_get(url, proxy, no_proxy, NULL /* bio */, NULL /* rbio */, + app_http_tls_cb, &info, 0 /* buf_size */, headers, + expected_content_type, 1 /* expect_asn1 */, + OSSL_HTTP_DEFAULT_MAX_RESP_LEN, timeout); + resp = ASN1_item_d2i_bio(it, mem, NULL); + BIO_free(mem); + + end: + OPENSSL_free(server); + OPENSSL_free(port); + return resp; + +} + +ASN1_VALUE *app_http_post_asn1(const char *host, const char *port, + const char *path, const char *proxy, + const char *no_proxy, SSL_CTX *ssl_ctx, + const STACK_OF(CONF_VALUE) *headers, + const char *content_type, + ASN1_VALUE *req, const ASN1_ITEM *req_it, + const char *expected_content_type, + long timeout, const ASN1_ITEM *rsp_it) +{ + int use_ssl = ssl_ctx != NULL; + APP_HTTP_TLS_INFO info; + BIO *rsp, *req_mem = ASN1_item_i2d_mem_bio(req_it, req); + ASN1_VALUE *res; + + if (req_mem == NULL) + return NULL; + + info.server = host; + info.port = port; + info.use_proxy = /* workaround for callback design flaw, see #17088 */ + OSSL_HTTP_adapt_proxy(proxy, no_proxy, host, use_ssl) != NULL; + info.timeout = timeout; + info.ssl_ctx = ssl_ctx; + rsp = OSSL_HTTP_transfer(NULL, host, port, path, use_ssl, + proxy, no_proxy, NULL /* bio */, NULL /* rbio */, + app_http_tls_cb, &info, + 0 /* buf_size */, headers, content_type, req_mem, + expected_content_type, 1 /* expect_asn1 */, + OSSL_HTTP_DEFAULT_MAX_RESP_LEN, timeout, + 0 /* keep_alive */); + BIO_free(req_mem); + res = ASN1_item_d2i_bio(rsp_it, rsp, NULL); + BIO_free(rsp); + return res; +} + +#endif + +/* + * Platform-specific sections + */ +#if defined(_WIN32) +# ifdef fileno +# undef fileno +# define fileno(a) (int)_fileno(a) +# endif + +# include <windows.h> +# include <tchar.h> + +static int WIN32_rename(const char *from, const char *to) +{ + TCHAR *tfrom = NULL, *tto; + DWORD err; + int ret = 0; + + if (sizeof(TCHAR) == 1) { + tfrom = (TCHAR *)from; + tto = (TCHAR *)to; + } else { /* UNICODE path */ + + size_t i, flen = strlen(from) + 1, tlen = strlen(to) + 1; + tfrom = malloc(sizeof(*tfrom) * (flen + tlen)); + if (tfrom == NULL) + goto err; + tto = tfrom + flen; +# if !defined(_WIN32_WCE) || _WIN32_WCE>=101 + if (!MultiByteToWideChar(CP_ACP, 0, from, flen, (WCHAR *)tfrom, flen)) +# endif + for (i = 0; i < flen; i++) + tfrom[i] = (TCHAR)from[i]; +# if !defined(_WIN32_WCE) || _WIN32_WCE>=101 + if (!MultiByteToWideChar(CP_ACP, 0, to, tlen, (WCHAR *)tto, tlen)) +# endif + for (i = 0; i < tlen; i++) + tto[i] = (TCHAR)to[i]; + } + + if (MoveFile(tfrom, tto)) + goto ok; + err = GetLastError(); + if (err == ERROR_ALREADY_EXISTS || err == ERROR_FILE_EXISTS) { + if (DeleteFile(tto) && MoveFile(tfrom, tto)) + goto ok; + err = GetLastError(); + } + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) + errno = ENOENT; + else if (err == ERROR_ACCESS_DENIED) + errno = EACCES; + else + errno = EINVAL; /* we could map more codes... */ + err: + ret = -1; + ok: + if (tfrom != NULL && tfrom != (TCHAR *)from) + free(tfrom); + return ret; +} +#endif + +/* app_tminterval section */ +#if defined(_WIN32) +double app_tminterval(int stop, int usertime) +{ + FILETIME now; + double ret = 0; + static ULARGE_INTEGER tmstart; + static int warning = 1; +# ifdef _WIN32_WINNT + static HANDLE proc = NULL; + + if (proc == NULL) { + if (check_winnt()) + proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, + GetCurrentProcessId()); + if (proc == NULL) + proc = (HANDLE) - 1; + } + + if (usertime && proc != (HANDLE) - 1) { + FILETIME junk; + GetProcessTimes(proc, &junk, &junk, &junk, &now); + } else +# endif + { + SYSTEMTIME systime; + + if (usertime && warning) { + BIO_printf(bio_err, "To get meaningful results, run " + "this program on idle system.\n"); + warning = 0; + } + GetSystemTime(&systime); + SystemTimeToFileTime(&systime, &now); + } + + if (stop == TM_START) { + tmstart.u.LowPart = now.dwLowDateTime; + tmstart.u.HighPart = now.dwHighDateTime; + } else { + ULARGE_INTEGER tmstop; + + tmstop.u.LowPart = now.dwLowDateTime; + tmstop.u.HighPart = now.dwHighDateTime; + + ret = (__int64)(tmstop.QuadPart - tmstart.QuadPart) * 1e-7; + } + + return ret; +} +#elif defined(OPENSSL_SYS_VXWORKS) +# include <time.h> + +double app_tminterval(int stop, int usertime) +{ + double ret = 0; +# ifdef CLOCK_REALTIME + static struct timespec tmstart; + struct timespec now; +# else + static unsigned long tmstart; + unsigned long now; +# endif + static int warning = 1; + + if (usertime && warning) { + BIO_printf(bio_err, "To get meaningful results, run " + "this program on idle system.\n"); + warning = 0; + } +# ifdef CLOCK_REALTIME + clock_gettime(CLOCK_REALTIME, &now); + if (stop == TM_START) + tmstart = now; + else + ret = ((now.tv_sec + now.tv_nsec * 1e-9) + - (tmstart.tv_sec + tmstart.tv_nsec * 1e-9)); +# else + now = tickGet(); + if (stop == TM_START) + tmstart = now; + else + ret = (now - tmstart) / (double)sysClkRateGet(); +# endif + return ret; +} + +#elif defined(_SC_CLK_TCK) /* by means of unistd.h */ +# include <sys/times.h> + +double app_tminterval(int stop, int usertime) +{ + double ret = 0; + struct tms rus; + clock_t now = times(&rus); + static clock_t tmstart; + + if (usertime) + now = rus.tms_utime; + + if (stop == TM_START) { + tmstart = now; + } else { + long int tck = sysconf(_SC_CLK_TCK); + ret = (now - tmstart) / (double)tck; + } + + return ret; +} + +#else +# include <sys/time.h> +# include <sys/resource.h> + +double app_tminterval(int stop, int usertime) +{ + double ret = 0; + struct rusage rus; + struct timeval now; + static struct timeval tmstart; + + if (usertime) + getrusage(RUSAGE_SELF, &rus), now = rus.ru_utime; + else + gettimeofday(&now, NULL); + + if (stop == TM_START) + tmstart = now; + else + ret = ((now.tv_sec + now.tv_usec * 1e-6) + - (tmstart.tv_sec + tmstart.tv_usec * 1e-6)); + + return ret; +} +#endif + +int app_access(const char* name, int flag) +{ +#ifdef _WIN32 + return _access(name, flag); +#else + return access(name, flag); +#endif +} + +int app_isdir(const char *name) +{ + return opt_isdir(name); +} + +/* raw_read|write section */ +#if defined(__VMS) +# include "vms_term_sock.h" +static int stdin_sock = -1; + +static void close_stdin_sock(void) +{ + TerminalSocket (TERM_SOCK_DELETE, &stdin_sock); +} + +int fileno_stdin(void) +{ + if (stdin_sock == -1) { + TerminalSocket(TERM_SOCK_CREATE, &stdin_sock); + atexit(close_stdin_sock); + } + + return stdin_sock; +} +#else +int fileno_stdin(void) +{ + return fileno(stdin); +} +#endif + +int fileno_stdout(void) +{ + return fileno(stdout); +} + +#if defined(_WIN32) && defined(STD_INPUT_HANDLE) +int raw_read_stdin(void *buf, int siz) +{ + DWORD n; + if (ReadFile(GetStdHandle(STD_INPUT_HANDLE), buf, siz, &n, NULL)) + return n; + else + return -1; +} +#elif defined(__VMS) +# include <sys/socket.h> + +int raw_read_stdin(void *buf, int siz) +{ + return recv(fileno_stdin(), buf, siz, 0); +} +#else +# if defined(__TANDEM) +# if defined(OPENSSL_TANDEM_FLOSS) +# include <floss.h(floss_read)> +# endif +# endif +int raw_read_stdin(void *buf, int siz) +{ + return read(fileno_stdin(), buf, siz); +} +#endif + +#if defined(_WIN32) && defined(STD_OUTPUT_HANDLE) +int raw_write_stdout(const void *buf, int siz) +{ + DWORD n; + if (WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, siz, &n, NULL)) + return n; + else + return -1; +} +#elif defined(OPENSSL_SYS_TANDEM) && defined(OPENSSL_THREADS) && defined(_SPT_MODEL_) +# if defined(__TANDEM) +# if defined(OPENSSL_TANDEM_FLOSS) +# include <floss.h(floss_write)> +# endif +# endif +int raw_write_stdout(const void *buf,int siz) +{ + return write(fileno(stdout),(void*)buf,siz); +} +#else +# if defined(__TANDEM) +# if defined(OPENSSL_TANDEM_FLOSS) +# include <floss.h(floss_write)> +# endif +# endif +int raw_write_stdout(const void *buf, int siz) +{ + return write(fileno_stdout(), buf, siz); +} +#endif + +/* + * Centralized handling of input and output files with format specification + * The format is meant to show what the input and output is supposed to be, + * and is therefore a show of intent more than anything else. However, it + * does impact behavior on some platforms, such as differentiating between + * text and binary input/output on non-Unix platforms + */ +BIO *dup_bio_in(int format) +{ + return BIO_new_fp(stdin, + BIO_NOCLOSE | (FMT_istext(format) ? BIO_FP_TEXT : 0)); +} + +BIO *dup_bio_out(int format) +{ + BIO *b = BIO_new_fp(stdout, + BIO_NOCLOSE | (FMT_istext(format) ? BIO_FP_TEXT : 0)); + void *prefix = NULL; + + if (b == NULL) + return NULL; + +#ifdef OPENSSL_SYS_VMS + if (FMT_istext(format)) + b = BIO_push(BIO_new(BIO_f_linebuffer()), b); +#endif + + if (FMT_istext(format) + && (prefix = getenv("HARNESS_OSSL_PREFIX")) != NULL) { + b = BIO_push(BIO_new(BIO_f_prefix()), b); + BIO_set_prefix(b, prefix); + } + + return b; +} + +BIO *dup_bio_err(int format) +{ + BIO *b = BIO_new_fp(stderr, + BIO_NOCLOSE | (FMT_istext(format) ? BIO_FP_TEXT : 0)); +#ifdef OPENSSL_SYS_VMS + if (b != NULL && FMT_istext(format)) + b = BIO_push(BIO_new(BIO_f_linebuffer()), b); +#endif + return b; +} + +void unbuffer(FILE *fp) +{ +/* + * On VMS, setbuf() will only take 32-bit pointers, and a compilation + * with /POINTER_SIZE=64 will give off a MAYLOSEDATA2 warning here. + * However, we trust that the C RTL will never give us a FILE pointer + * above the first 4 GB of memory, so we simply turn off the warning + * temporarily. + */ +#if defined(OPENSSL_SYS_VMS) && defined(__DECC) +# pragma environment save +# pragma message disable maylosedata2 +#endif + setbuf(fp, NULL); +#if defined(OPENSSL_SYS_VMS) && defined(__DECC) +# pragma environment restore +#endif +} + +static const char *modestr(char mode, int format) +{ + OPENSSL_assert(mode == 'a' || mode == 'r' || mode == 'w'); + + switch (mode) { + case 'a': + return FMT_istext(format) ? "a" : "ab"; + case 'r': + return FMT_istext(format) ? "r" : "rb"; + case 'w': + return FMT_istext(format) ? "w" : "wb"; + } + /* The assert above should make sure we never reach this point */ + return NULL; +} + +static const char *modeverb(char mode) +{ + switch (mode) { + case 'a': + return "appending"; + case 'r': + return "reading"; + case 'w': + return "writing"; + } + return "(doing something)"; +} + +/* + * Open a file for writing, owner-read-only. + */ +BIO *bio_open_owner(const char *filename, int format, int private) +{ + FILE *fp = NULL; + BIO *b = NULL; + int textmode, bflags; +#ifndef OPENSSL_NO_POSIX_IO + int fd = -1, mode; +#endif + + if (!private || filename == NULL || strcmp(filename, "-") == 0) + return bio_open_default(filename, 'w', format); + + textmode = FMT_istext(format); +#ifndef OPENSSL_NO_POSIX_IO + mode = O_WRONLY; +# ifdef O_CREAT + mode |= O_CREAT; +# endif +# ifdef O_TRUNC + mode |= O_TRUNC; +# endif + if (!textmode) { +# ifdef O_BINARY + mode |= O_BINARY; +# elif defined(_O_BINARY) + mode |= _O_BINARY; +# endif + } + +# ifdef OPENSSL_SYS_VMS + /* VMS doesn't have O_BINARY, it just doesn't make sense. But, + * it still needs to know that we're going binary, or fdopen() + * will fail with "invalid argument"... so we tell VMS what the + * context is. + */ + if (!textmode) + fd = open(filename, mode, 0600, "ctx=bin"); + else +# endif + fd = open(filename, mode, 0600); + if (fd < 0) + goto err; + fp = fdopen(fd, modestr('w', format)); +#else /* OPENSSL_NO_POSIX_IO */ + /* Have stdio but not Posix IO, do the best we can */ + fp = fopen(filename, modestr('w', format)); +#endif /* OPENSSL_NO_POSIX_IO */ + if (fp == NULL) + goto err; + bflags = BIO_CLOSE; + if (textmode) + bflags |= BIO_FP_TEXT; + b = BIO_new_fp(fp, bflags); + if (b != NULL) + return b; + + err: + BIO_printf(bio_err, "%s: Can't open \"%s\" for writing, %s\n", + opt_getprog(), filename, strerror(errno)); + ERR_print_errors(bio_err); + /* If we have fp, then fdopen took over fd, so don't close both. */ + if (fp != NULL) + fclose(fp); +#ifndef OPENSSL_NO_POSIX_IO + else if (fd >= 0) + close(fd); +#endif + return NULL; +} + +static BIO *bio_open_default_(const char *filename, char mode, int format, + int quiet) +{ + BIO *ret; + + if (filename == NULL || strcmp(filename, "-") == 0) { + ret = mode == 'r' ? dup_bio_in(format) : dup_bio_out(format); + if (quiet) { + ERR_clear_error(); + return ret; + } + if (ret != NULL) + return ret; + BIO_printf(bio_err, + "Can't open %s, %s\n", + mode == 'r' ? "stdin" : "stdout", strerror(errno)); + } else { + ret = BIO_new_file(filename, modestr(mode, format)); + if (quiet) { + ERR_clear_error(); + return ret; + } + if (ret != NULL) + return ret; + BIO_printf(bio_err, + "Can't open \"%s\" for %s, %s\n", + filename, modeverb(mode), strerror(errno)); + } + ERR_print_errors(bio_err); + return NULL; +} + +BIO *bio_open_default(const char *filename, char mode, int format) +{ + return bio_open_default_(filename, mode, format, 0); +} + +BIO *bio_open_default_quiet(const char *filename, char mode, int format) +{ + return bio_open_default_(filename, mode, format, 1); +} + +void wait_for_async(SSL *s) +{ + /* On Windows select only works for sockets, so we simply don't wait */ +#ifndef OPENSSL_SYS_WINDOWS + int width = 0; + fd_set asyncfds; + OSSL_ASYNC_FD *fds; + size_t numfds; + size_t i; + + if (!SSL_get_all_async_fds(s, NULL, &numfds)) + return; + if (numfds == 0) + return; + fds = app_malloc(sizeof(OSSL_ASYNC_FD) * numfds, "allocate async fds"); + if (!SSL_get_all_async_fds(s, fds, &numfds)) { + OPENSSL_free(fds); + return; + } + + FD_ZERO(&asyncfds); + for (i = 0; i < numfds; i++) { + if (width <= (int)fds[i]) + width = (int)fds[i] + 1; + openssl_fdset((int)fds[i], &asyncfds); + } + select(width, (void *)&asyncfds, NULL, NULL, NULL); + OPENSSL_free(fds); +#endif +} + +/* if OPENSSL_SYS_WINDOWS is defined then so is OPENSSL_SYS_MSDOS */ +#if defined(OPENSSL_SYS_MSDOS) +int has_stdin_waiting(void) +{ +# if defined(OPENSSL_SYS_WINDOWS) + HANDLE inhand = GetStdHandle(STD_INPUT_HANDLE); + DWORD events = 0; + INPUT_RECORD inputrec; + DWORD insize = 1; + BOOL peeked; + + if (inhand == INVALID_HANDLE_VALUE) { + return 0; + } + + peeked = PeekConsoleInput(inhand, &inputrec, insize, &events); + if (!peeked) { + /* Probably redirected input? _kbhit() does not work in this case */ + if (!feof(stdin)) { + return 1; + } + return 0; + } +# endif + return _kbhit(); +} +#endif + +/* Corrupt a signature by modifying final byte */ +void corrupt_signature(const ASN1_STRING *signature) +{ + unsigned char *s = signature->data; + s[signature->length - 1] ^= 0x1; +} + +int set_cert_times(X509 *x, const char *startdate, const char *enddate, + int days) +{ + if (startdate == NULL || strcmp(startdate, "today") == 0) { + if (X509_gmtime_adj(X509_getm_notBefore(x), 0) == NULL) + return 0; + } else { + if (!ASN1_TIME_set_string_X509(X509_getm_notBefore(x), startdate)) + return 0; + } + if (enddate == NULL) { + if (X509_time_adj_ex(X509_getm_notAfter(x), days, 0, NULL) + == NULL) + return 0; + } else if (!ASN1_TIME_set_string_X509(X509_getm_notAfter(x), enddate)) { + return 0; + } + return 1; +} + +int set_crl_lastupdate(X509_CRL *crl, const char *lastupdate) +{ + int ret = 0; + ASN1_TIME *tm = ASN1_TIME_new(); + + if (tm == NULL) + goto end; + + if (lastupdate == NULL) { + if (X509_gmtime_adj(tm, 0) == NULL) + goto end; + } else { + if (!ASN1_TIME_set_string_X509(tm, lastupdate)) + goto end; + } + + if (!X509_CRL_set1_lastUpdate(crl, tm)) + goto end; + + ret = 1; +end: + ASN1_TIME_free(tm); + return ret; +} + +int set_crl_nextupdate(X509_CRL *crl, const char *nextupdate, + long days, long hours, long secs) +{ + int ret = 0; + ASN1_TIME *tm = ASN1_TIME_new(); + + if (tm == NULL) + goto end; + + if (nextupdate == NULL) { + if (X509_time_adj_ex(tm, days, hours * 60 * 60 + secs, NULL) == NULL) + goto end; + } else { + if (!ASN1_TIME_set_string_X509(tm, nextupdate)) + goto end; + } + + if (!X509_CRL_set1_nextUpdate(crl, tm)) + goto end; + + ret = 1; +end: + ASN1_TIME_free(tm); + return ret; +} + +void make_uppercase(char *string) +{ + int i; + + for (i = 0; string[i] != '\0'; i++) + string[i] = toupper((unsigned char)string[i]); +} + +/* This function is defined here due to visibility of bio_err */ +int opt_printf_stderr(const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = BIO_vprintf(bio_err, fmt, ap); + va_end(ap); + return ret; +} + +OSSL_PARAM *app_params_new_from_opts(STACK_OF(OPENSSL_STRING) *opts, + const OSSL_PARAM *paramdefs) +{ + OSSL_PARAM *params = NULL; + size_t sz = (size_t)sk_OPENSSL_STRING_num(opts); + size_t params_n; + char *opt = "", *stmp, *vtmp = NULL; + int found = 1; + + if (opts == NULL) + return NULL; + + params = OPENSSL_zalloc(sizeof(OSSL_PARAM) * (sz + 1)); + if (params == NULL) + return NULL; + + for (params_n = 0; params_n < sz; params_n++) { + opt = sk_OPENSSL_STRING_value(opts, (int)params_n); + if ((stmp = OPENSSL_strdup(opt)) == NULL + || (vtmp = strchr(stmp, ':')) == NULL) + goto err; + /* Replace ':' with 0 to terminate the string pointed to by stmp */ + *vtmp = 0; + /* Skip over the separator so that vmtp points to the value */ + vtmp++; + if (!OSSL_PARAM_allocate_from_text(¶ms[params_n], paramdefs, + stmp, vtmp, strlen(vtmp), &found)) + goto err; + OPENSSL_free(stmp); + } + params[params_n] = OSSL_PARAM_construct_end(); + return params; +err: + OPENSSL_free(stmp); + BIO_printf(bio_err, "Parameter %s '%s'\n", found ? "error" : "unknown", + opt); + ERR_print_errors(bio_err); + app_params_free(params); + return NULL; +} + +void app_params_free(OSSL_PARAM *params) +{ + int i; + + if (params != NULL) { + for (i = 0; params[i].key != NULL; ++i) + OPENSSL_free(params[i].data); + OPENSSL_free(params); + } +} + +EVP_PKEY *app_keygen(EVP_PKEY_CTX *ctx, const char *alg, int bits, int verbose) +{ + EVP_PKEY *res = NULL; + + if (verbose && alg != NULL) { + BIO_printf(bio_err, "Generating %s key", alg); + if (bits > 0) + BIO_printf(bio_err, " with %d bits\n", bits); + else + BIO_printf(bio_err, "\n"); + } + if (!RAND_status()) + BIO_printf(bio_err, "Warning: generating random key material may take a long time\n" + "if the system has a poor entropy source\n"); + if (EVP_PKEY_keygen(ctx, &res) <= 0) + BIO_printf(bio_err, "%s: Error generating %s key\n", opt_getprog(), + alg != NULL ? alg : "asymmetric"); + return res; +} + +EVP_PKEY *app_paramgen(EVP_PKEY_CTX *ctx, const char *alg) +{ + EVP_PKEY *res = NULL; + + if (!RAND_status()) + BIO_printf(bio_err, "Warning: generating random key parameters may take a long time\n" + "if the system has a poor entropy source\n"); + if (EVP_PKEY_paramgen(ctx, &res) <= 0) + BIO_printf(bio_err, "%s: Generating %s key parameters failed\n", + opt_getprog(), alg != NULL ? alg : "asymmetric"); + return res; +} + +/* + * Return non-zero if the legacy path is still an option. + * This decision is based on the global command line operations and the + * behaviour thus far. + */ +int opt_legacy_okay(void) +{ + int provider_options = opt_provider_option_given(); + int libctx = app_get0_libctx() != NULL || app_get0_propq() != NULL; + /* + * Having a provider option specified or a custom library context or + * property query, is a sure sign we're not using legacy. + */ + if (provider_options || libctx) + return 0; + return 1; +} diff --git a/apps/lib/apps_ui.c b/apps/lib/apps_ui.c new file mode 100644 index 000000000000..00e0ba5d9996 --- /dev/null +++ b/apps/lib/apps_ui.c @@ -0,0 +1,223 @@ +/* + * Copyright 1995-2020 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 <string.h> +#include <openssl/err.h> +#include <openssl/ui.h> +#include "apps_ui.h" + +static UI_METHOD *ui_method = NULL; +static const UI_METHOD *ui_base_method = NULL; + +static int ui_open(UI *ui) +{ + int (*opener)(UI *ui) = UI_method_get_opener(ui_base_method); + + if (opener != NULL) + return opener(ui); + return 1; +} + +static int ui_read(UI *ui, UI_STRING *uis) +{ + int (*reader)(UI *ui, UI_STRING *uis) = NULL; + + if (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD + && UI_get0_user_data(ui)) { + switch (UI_get_string_type(uis)) { + case UIT_PROMPT: + case UIT_VERIFY: + { + const char *password = + ((PW_CB_DATA *)UI_get0_user_data(ui))->password; + + if (password != NULL) { + UI_set_result(ui, uis, password); + return 1; + } + } + break; + case UIT_NONE: + case UIT_BOOLEAN: + case UIT_INFO: + case UIT_ERROR: + break; + } + } + + reader = UI_method_get_reader(ui_base_method); + if (reader != NULL) + return reader(ui, uis); + /* Default to the empty password if we've got nothing better */ + UI_set_result(ui, uis, ""); + return 1; +} + +static int ui_write(UI *ui, UI_STRING *uis) +{ + int (*writer)(UI *ui, UI_STRING *uis) = NULL; + + if (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD + && UI_get0_user_data(ui)) { + switch (UI_get_string_type(uis)) { + case UIT_PROMPT: + case UIT_VERIFY: + { + const char *password = + ((PW_CB_DATA *)UI_get0_user_data(ui))->password; + + if (password != NULL) + return 1; + } + break; + case UIT_NONE: + case UIT_BOOLEAN: + case UIT_INFO: + case UIT_ERROR: + break; + } + } + + writer = UI_method_get_writer(ui_base_method); + if (writer != NULL) + return writer(ui, uis); + return 1; +} + +static int ui_close(UI *ui) +{ + int (*closer)(UI *ui) = UI_method_get_closer(ui_base_method); + + if (closer != NULL) + return closer(ui); + return 1; +} + +/* object_name defaults to prompt_info from ui user data if present */ +static char *ui_prompt_construct(UI *ui, const char *phrase_desc, + const char *object_name) +{ + PW_CB_DATA *cb_data = (PW_CB_DATA *)UI_get0_user_data(ui); + + if (phrase_desc == NULL) + phrase_desc = "pass phrase"; + if (object_name == NULL && cb_data != NULL) + object_name = cb_data->prompt_info; + return UI_construct_prompt(NULL, phrase_desc, object_name); +} + +int set_base_ui_method(const UI_METHOD *ui_meth) +{ + if (ui_meth == NULL) + ui_meth = UI_null(); + ui_base_method = ui_meth; + return 1; +} + +int setup_ui_method(void) +{ + ui_base_method = UI_null(); +#ifndef OPENSSL_NO_UI_CONSOLE + ui_base_method = UI_OpenSSL(); +#endif + ui_method = UI_create_method("OpenSSL application user interface"); + return ui_method != NULL + && 0 == UI_method_set_opener(ui_method, ui_open) + && 0 == UI_method_set_reader(ui_method, ui_read) + && 0 == UI_method_set_writer(ui_method, ui_write) + && 0 == UI_method_set_closer(ui_method, ui_close) + && 0 == UI_method_set_prompt_constructor(ui_method, + ui_prompt_construct); +} + +void destroy_ui_method(void) +{ + if (ui_method != NULL) { + UI_destroy_method(ui_method); + ui_method = NULL; + } +} + +const UI_METHOD *get_ui_method(void) +{ + return ui_method; +} + +static void *ui_malloc(int sz, const char *what) +{ + void *vp = OPENSSL_malloc(sz); + + if (vp == NULL) { + BIO_printf(bio_err, "Could not allocate %d bytes for %s\n", sz, what); + ERR_print_errors(bio_err); + exit(1); + } + return vp; +} + +int password_callback(char *buf, int bufsiz, int verify, PW_CB_DATA *cb_data) +{ + int res = 0; + UI *ui; + int ok = 0; + char *buff = NULL; + int ui_flags = 0; + const char *prompt_info = NULL; + char *prompt; + + if ((ui = UI_new_method(ui_method)) == NULL) + return 0; + + if (cb_data != NULL && cb_data->prompt_info != NULL) + prompt_info = cb_data->prompt_info; + prompt = UI_construct_prompt(ui, "pass phrase", prompt_info); + if (prompt == NULL) { + BIO_printf(bio_err, "Out of memory\n"); + UI_free(ui); + return 0; + } + + ui_flags |= UI_INPUT_FLAG_DEFAULT_PWD; + UI_ctrl(ui, UI_CTRL_PRINT_ERRORS, 1, 0, 0); + + /* We know that there is no previous user data to return to us */ + (void)UI_add_user_data(ui, cb_data); + + ok = UI_add_input_string(ui, prompt, ui_flags, buf, + PW_MIN_LENGTH, bufsiz - 1); + + if (ok >= 0 && verify) { + buff = ui_malloc(bufsiz, "password buffer"); + ok = UI_add_verify_string(ui, prompt, ui_flags, buff, + PW_MIN_LENGTH, bufsiz - 1, buf); + } + if (ok >= 0) + do { + ok = UI_process(ui); + } while (ok < 0 && UI_ctrl(ui, UI_CTRL_IS_REDOABLE, 0, 0, 0)); + + OPENSSL_clear_free(buff, (unsigned int)bufsiz); + + if (ok >= 0) + res = strlen(buf); + if (ok == -1) { + BIO_printf(bio_err, "User interface error\n"); + ERR_print_errors(bio_err); + OPENSSL_cleanse(buf, (unsigned int)bufsiz); + res = 0; + } + if (ok == -2) { + BIO_printf(bio_err, "aborted!\n"); + OPENSSL_cleanse(buf, (unsigned int)bufsiz); + res = 0; + } + UI_free(ui); + OPENSSL_free(prompt); + return res; +} diff --git a/apps/lib/build.info b/apps/lib/build.info new file mode 100644 index 000000000000..923ef5d92b83 --- /dev/null +++ b/apps/lib/build.info @@ -0,0 +1,23 @@ +# Auxiliary program source +IF[{- $config{target} =~ /^(?:VC-|mingw|BC-)/ -}] + # It's called 'init', but doesn't have much 'init' in it... + $AUXLIBAPPSSRC=win32_init.c +ENDIF +IF[{- $config{target} =~ /^vms-/ -}] + $AUXLIBAPPSSRC=vms_term_sock.c vms_decc_argv.c +ENDIF + +# Source for libapps +$LIBAPPSSRC=apps.c apps_ui.c opt.c fmt.c s_cb.c s_socket.c app_rand.c \ + columns.c app_params.c names.c app_provider.c app_x509.c http_server.c \ + engine.c engine_loader.c app_libctx.c + +IF[{- !$disabled{apps} -}] + LIBS{noinst}=../libapps.a + SOURCE[../libapps.a]=$LIBAPPSSRC $AUXLIBAPPSSRC + INCLUDE[../libapps.a]=../.. ../../include ../include +ENDIF + +IF[{- !$disabled{srp} -}] + SOURCE[../libapps.a]=tlssrp_depr.c +ENDIF diff --git a/apps/lib/cmp_mock_srv.c b/apps/lib/cmp_mock_srv.c new file mode 100644 index 000000000000..637bd1d0b7a4 --- /dev/null +++ b/apps/lib/cmp_mock_srv.c @@ -0,0 +1,452 @@ +/* + * Copyright 2018-2023 The OpenSSL Project Authors. All Rights Reserved. + * Copyright Siemens AG 2018-2020 + * + * 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 atf + * https://www.openssl.org/source/license.html + */ + +#include "apps.h" +#include "cmp_mock_srv.h" + +#include <openssl/cmp.h> +#include <openssl/err.h> +#include <openssl/cmperr.h> + +/* the context for the CMP mock server */ +typedef struct +{ + X509 *certOut; /* certificate to be returned in cp/ip/kup msg */ + STACK_OF(X509) *chainOut; /* chain of certOut to add to extraCerts field */ + STACK_OF(X509) *caPubsOut; /* certs to return in caPubs field of ip msg */ + OSSL_CMP_PKISI *statusOut; /* status for ip/cp/kup/rp msg unless polling */ + int sendError; /* send error response on given request type */ + OSSL_CMP_MSG *certReq; /* ir/cr/p10cr/kur remembered while polling */ + int pollCount; /* number of polls before actual cert response */ + int curr_pollCount; /* number of polls so far for current request */ + int checkAfterTime; /* time the client should wait between polling */ +} mock_srv_ctx; + + +static void mock_srv_ctx_free(mock_srv_ctx *ctx) +{ + if (ctx == NULL) + return; + + OSSL_CMP_PKISI_free(ctx->statusOut); + X509_free(ctx->certOut); + sk_X509_pop_free(ctx->chainOut, X509_free); + sk_X509_pop_free(ctx->caPubsOut, X509_free); + OSSL_CMP_MSG_free(ctx->certReq); + OPENSSL_free(ctx); +} + +static mock_srv_ctx *mock_srv_ctx_new(void) +{ + mock_srv_ctx *ctx = OPENSSL_zalloc(sizeof(mock_srv_ctx)); + + if (ctx == NULL) + goto err; + + if ((ctx->statusOut = OSSL_CMP_PKISI_new()) == NULL) + goto err; + + ctx->sendError = -1; + + /* all other elements are initialized to 0 or NULL, respectively */ + return ctx; + err: + mock_srv_ctx_free(ctx); + return NULL; +} + +int ossl_cmp_mock_srv_set1_certOut(OSSL_CMP_SRV_CTX *srv_ctx, X509 *cert) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + + if (ctx == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + if (cert == NULL || X509_up_ref(cert)) { + X509_free(ctx->certOut); + ctx->certOut = cert; + return 1; + } + return 0; +} + +int ossl_cmp_mock_srv_set1_chainOut(OSSL_CMP_SRV_CTX *srv_ctx, + STACK_OF(X509) *chain) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + STACK_OF(X509) *chain_copy = NULL; + + if (ctx == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + if (chain != NULL && (chain_copy = X509_chain_up_ref(chain)) == NULL) + return 0; + sk_X509_pop_free(ctx->chainOut, X509_free); + ctx->chainOut = chain_copy; + return 1; +} + +int ossl_cmp_mock_srv_set1_caPubsOut(OSSL_CMP_SRV_CTX *srv_ctx, + STACK_OF(X509) *caPubs) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + STACK_OF(X509) *caPubs_copy = NULL; + + if (ctx == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + if (caPubs != NULL && (caPubs_copy = X509_chain_up_ref(caPubs)) == NULL) + return 0; + sk_X509_pop_free(ctx->caPubsOut, X509_free); + ctx->caPubsOut = caPubs_copy; + return 1; +} + +int ossl_cmp_mock_srv_set_statusInfo(OSSL_CMP_SRV_CTX *srv_ctx, int status, + int fail_info, const char *text) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + OSSL_CMP_PKISI *si; + + if (ctx == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + if ((si = OSSL_CMP_STATUSINFO_new(status, fail_info, text)) == NULL) + return 0; + OSSL_CMP_PKISI_free(ctx->statusOut); + ctx->statusOut = si; + return 1; +} + +int ossl_cmp_mock_srv_set_sendError(OSSL_CMP_SRV_CTX *srv_ctx, int bodytype) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + + if (ctx == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + /* might check bodytype, but this would require exporting all body types */ + ctx->sendError = bodytype; + return 1; +} + +int ossl_cmp_mock_srv_set_pollCount(OSSL_CMP_SRV_CTX *srv_ctx, int count) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + + if (ctx == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + if (count < 0) { + ERR_raise(ERR_LIB_CMP, CMP_R_INVALID_ARGS); + return 0; + } + ctx->pollCount = count; + return 1; +} + +int ossl_cmp_mock_srv_set_checkAfterTime(OSSL_CMP_SRV_CTX *srv_ctx, int sec) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + + if (ctx == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + ctx->checkAfterTime = sec; + return 1; +} + +static OSSL_CMP_PKISI *process_cert_request(OSSL_CMP_SRV_CTX *srv_ctx, + const OSSL_CMP_MSG *cert_req, + ossl_unused int certReqId, + const OSSL_CRMF_MSG *crm, + const X509_REQ *p10cr, + X509 **certOut, + STACK_OF(X509) **chainOut, + STACK_OF(X509) **caPubs) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + OSSL_CMP_PKISI *si = NULL; + + if (ctx == NULL || cert_req == NULL + || certOut == NULL || chainOut == NULL || caPubs == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return NULL; + } + if (ctx->sendError == 1 + || ctx->sendError == OSSL_CMP_MSG_get_bodytype(cert_req)) { + ERR_raise(ERR_LIB_CMP, CMP_R_ERROR_PROCESSING_MESSAGE); + return NULL; + } + + *certOut = NULL; + *chainOut = NULL; + *caPubs = NULL; + + if (ctx->pollCount > 0 && ctx->curr_pollCount == 0) { + /* start polling */ + if (ctx->certReq != NULL) { + /* already in polling mode */ + ERR_raise(ERR_LIB_CMP, CMP_R_UNEXPECTED_PKIBODY); + return NULL; + } + if ((ctx->certReq = OSSL_CMP_MSG_dup(cert_req)) == NULL) + return NULL; + return OSSL_CMP_STATUSINFO_new(OSSL_CMP_PKISTATUS_waiting, 0, NULL); + } + if (ctx->curr_pollCount >= ctx->pollCount) + /* give final response after polling */ + ctx->curr_pollCount = 0; + + if (OSSL_CMP_MSG_get_bodytype(cert_req) == OSSL_CMP_KUR + && crm != NULL && ctx->certOut != NULL) { + const OSSL_CRMF_CERTID *cid = OSSL_CRMF_MSG_get0_regCtrl_oldCertID(crm); + const X509_NAME *issuer = X509_get_issuer_name(ctx->certOut); + const ASN1_INTEGER *serial = X509_get0_serialNumber(ctx->certOut); + + if (cid == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_MISSING_CERTID); + return NULL; + } + if (issuer != NULL + && X509_NAME_cmp(issuer, OSSL_CRMF_CERTID_get0_issuer(cid)) != 0) { + ERR_raise(ERR_LIB_CMP, CMP_R_WRONG_CERTID); + return NULL; + } + if (serial != NULL + && ASN1_INTEGER_cmp(serial, + OSSL_CRMF_CERTID_get0_serialNumber(cid)) != 0) { + ERR_raise(ERR_LIB_CMP, CMP_R_WRONG_CERTID); + return NULL; + } + } + + if (ctx->certOut != NULL + && (*certOut = X509_dup(ctx->certOut)) == NULL) + goto err; + if (ctx->chainOut != NULL + && (*chainOut = X509_chain_up_ref(ctx->chainOut)) == NULL) + goto err; + if (ctx->caPubsOut != NULL + && (*caPubs = X509_chain_up_ref(ctx->caPubsOut)) == NULL) + goto err; + if (ctx->statusOut != NULL + && (si = OSSL_CMP_PKISI_dup(ctx->statusOut)) == NULL) + goto err; + return si; + + err: + X509_free(*certOut); + *certOut = NULL; + sk_X509_pop_free(*chainOut, X509_free); + *chainOut = NULL; + sk_X509_pop_free(*caPubs, X509_free); + *caPubs = NULL; + return NULL; +} + +static OSSL_CMP_PKISI *process_rr(OSSL_CMP_SRV_CTX *srv_ctx, + const OSSL_CMP_MSG *rr, + const X509_NAME *issuer, + const ASN1_INTEGER *serial) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + + if (ctx == NULL || rr == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return NULL; + } + if (ctx->certOut == NULL || ctx->sendError == 1 + || ctx->sendError == OSSL_CMP_MSG_get_bodytype(rr)) { + ERR_raise(ERR_LIB_CMP, CMP_R_ERROR_PROCESSING_MESSAGE); + return NULL; + } + + /* Allow any RR derived from CSR, which may include subject and serial */ + if (issuer == NULL || serial == NULL) + return OSSL_CMP_PKISI_dup(ctx->statusOut); + + /* accept revocation only for the certificate we sent in ir/cr/kur */ + if (X509_NAME_cmp(issuer, X509_get_issuer_name(ctx->certOut)) != 0 + || ASN1_INTEGER_cmp(serial, + X509_get0_serialNumber(ctx->certOut)) != 0) { + ERR_raise_data(ERR_LIB_CMP, CMP_R_REQUEST_NOT_ACCEPTED, + "wrong certificate to revoke"); + return NULL; + } + return OSSL_CMP_PKISI_dup(ctx->statusOut); +} + +static int process_genm(OSSL_CMP_SRV_CTX *srv_ctx, + const OSSL_CMP_MSG *genm, + const STACK_OF(OSSL_CMP_ITAV) *in, + STACK_OF(OSSL_CMP_ITAV) **out) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + + if (ctx == NULL || genm == NULL || in == NULL || out == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + if (ctx->sendError == 1 + || ctx->sendError == OSSL_CMP_MSG_get_bodytype(genm) + || sk_OSSL_CMP_ITAV_num(in) > 1) { + ERR_raise(ERR_LIB_CMP, CMP_R_ERROR_PROCESSING_MESSAGE); + return 0; + } + + *out = sk_OSSL_CMP_ITAV_deep_copy(in, OSSL_CMP_ITAV_dup, + OSSL_CMP_ITAV_free); + return *out != NULL; +} + +static void process_error(OSSL_CMP_SRV_CTX *srv_ctx, const OSSL_CMP_MSG *error, + const OSSL_CMP_PKISI *statusInfo, + const ASN1_INTEGER *errorCode, + const OSSL_CMP_PKIFREETEXT *errorDetails) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + char buf[OSSL_CMP_PKISI_BUFLEN]; + char *sibuf; + int i; + + if (ctx == NULL || error == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return; + } + + BIO_printf(bio_err, "mock server received error:\n"); + + if (statusInfo == NULL) { + BIO_printf(bio_err, "pkiStatusInfo absent\n"); + } else { + sibuf = OSSL_CMP_snprint_PKIStatusInfo(statusInfo, buf, sizeof(buf)); + BIO_printf(bio_err, "pkiStatusInfo: %s\n", + sibuf != NULL ? sibuf: "<invalid>"); + } + + if (errorCode == NULL) + BIO_printf(bio_err, "errorCode absent\n"); + else + BIO_printf(bio_err, "errorCode: %ld\n", ASN1_INTEGER_get(errorCode)); + + if (sk_ASN1_UTF8STRING_num(errorDetails) <= 0) { + BIO_printf(bio_err, "errorDetails absent\n"); + } else { + BIO_printf(bio_err, "errorDetails: "); + for (i = 0; i < sk_ASN1_UTF8STRING_num(errorDetails); i++) { + if (i > 0) + BIO_printf(bio_err, ", "); + BIO_printf(bio_err, "\""); + ASN1_STRING_print(bio_err, + sk_ASN1_UTF8STRING_value(errorDetails, i)); + BIO_printf(bio_err, "\""); + } + BIO_printf(bio_err, "\n"); + } +} + +static int process_certConf(OSSL_CMP_SRV_CTX *srv_ctx, + const OSSL_CMP_MSG *certConf, + ossl_unused int certReqId, + const ASN1_OCTET_STRING *certHash, + const OSSL_CMP_PKISI *si) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + ASN1_OCTET_STRING *digest; + + if (ctx == NULL || certConf == NULL || certHash == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + if (ctx->sendError == 1 + || ctx->sendError == OSSL_CMP_MSG_get_bodytype(certConf) + || ctx->certOut == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_ERROR_PROCESSING_MESSAGE); + return 0; + } + + if ((digest = X509_digest_sig(ctx->certOut, NULL, NULL)) == NULL) + return 0; + if (ASN1_OCTET_STRING_cmp(certHash, digest) != 0) { + ASN1_OCTET_STRING_free(digest); + ERR_raise(ERR_LIB_CMP, CMP_R_CERTHASH_UNMATCHED); + return 0; + } + ASN1_OCTET_STRING_free(digest); + return 1; +} + +static int process_pollReq(OSSL_CMP_SRV_CTX *srv_ctx, + const OSSL_CMP_MSG *pollReq, + ossl_unused int certReqId, + OSSL_CMP_MSG **certReq, int64_t *check_after) +{ + mock_srv_ctx *ctx = OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx); + + if (ctx == NULL || pollReq == NULL + || certReq == NULL || check_after == NULL) { + ERR_raise(ERR_LIB_CMP, CMP_R_NULL_ARGUMENT); + return 0; + } + if (ctx->sendError == 1 + || ctx->sendError == OSSL_CMP_MSG_get_bodytype(pollReq)) { + *certReq = NULL; + ERR_raise(ERR_LIB_CMP, CMP_R_ERROR_PROCESSING_MESSAGE); + return 0; + } + if (ctx->certReq == NULL) { + /* not currently in polling mode */ + *certReq = NULL; + ERR_raise(ERR_LIB_CMP, CMP_R_UNEXPECTED_PKIBODY); + return 0; + } + + if (++ctx->curr_pollCount >= ctx->pollCount) { + /* end polling */ + *certReq = ctx->certReq; + ctx->certReq = NULL; + *check_after = 0; + } else { + *certReq = NULL; + *check_after = ctx->checkAfterTime; + } + return 1; +} + +OSSL_CMP_SRV_CTX *ossl_cmp_mock_srv_new(OSSL_LIB_CTX *libctx, const char *propq) +{ + OSSL_CMP_SRV_CTX *srv_ctx = OSSL_CMP_SRV_CTX_new(libctx, propq); + mock_srv_ctx *ctx = mock_srv_ctx_new(); + + if (srv_ctx != NULL && ctx != NULL + && OSSL_CMP_SRV_CTX_init(srv_ctx, ctx, process_cert_request, + process_rr, process_genm, process_error, + process_certConf, process_pollReq)) + return srv_ctx; + + mock_srv_ctx_free(ctx); + OSSL_CMP_SRV_CTX_free(srv_ctx); + return NULL; +} + +void ossl_cmp_mock_srv_free(OSSL_CMP_SRV_CTX *srv_ctx) +{ + if (srv_ctx != NULL) + mock_srv_ctx_free(OSSL_CMP_SRV_CTX_get0_custom_ctx(srv_ctx)); + OSSL_CMP_SRV_CTX_free(srv_ctx); +} diff --git a/apps/lib/columns.c b/apps/lib/columns.c new file mode 100644 index 000000000000..aa58fe1781f5 --- /dev/null +++ b/apps/lib/columns.c @@ -0,0 +1,27 @@ +/* + * Copyright 2017-2019 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 <string.h> +#include "apps.h" +#include "function.h" + +void calculate_columns(FUNCTION *functions, DISPLAY_COLUMNS *dc) +{ + FUNCTION *f; + int len, maxlen = 0; + + for (f = functions; f->name != NULL; ++f) + if (f->type == FT_general || f->type == FT_md || f->type == FT_cipher) + if ((len = strlen(f->name)) > maxlen) + maxlen = len; + + dc->width = maxlen + 2; + dc->columns = (80 - 1) / dc->width; +} + diff --git a/apps/lib/engine.c b/apps/lib/engine.c new file mode 100644 index 000000000000..209c4b6b03c2 --- /dev/null +++ b/apps/lib/engine.c @@ -0,0 +1,193 @@ +/* + * Copyright 2020 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 + */ + +/* + * Here is a set of wrappers for the ENGINE API, which are no-ops when the + * ENGINE API is disabled / removed. + * We need to suppress deprecation warnings to make this work. + */ +#define OPENSSL_SUPPRESS_DEPRECATED + +#include <string.h> /* strcmp */ + +#include <openssl/types.h> /* Ensure we have the ENGINE type, regardless */ +#include <openssl/err.h> +#ifndef OPENSSL_NO_ENGINE +# include <openssl/engine.h> +#endif +#include "apps.h" + +#ifndef OPENSSL_NO_ENGINE +/* Try to load an engine in a shareable library */ +static ENGINE *try_load_engine(const char *engine) +{ + ENGINE *e = NULL; + + if ((e = ENGINE_by_id("dynamic")) != NULL) { + if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine, 0) + || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) { + ENGINE_free(e); + e = NULL; + } + } + return e; +} +#endif + +ENGINE *setup_engine_methods(const char *id, unsigned int methods, int debug) +{ + ENGINE *e = NULL; + +#ifndef OPENSSL_NO_ENGINE + if (id != NULL) { + if (strcmp(id, "auto") == 0) { + BIO_printf(bio_err, "Enabling auto ENGINE support\n"); + ENGINE_register_all_complete(); + return NULL; + } + if ((e = ENGINE_by_id(id)) == NULL + && (e = try_load_engine(id)) == NULL) { + BIO_printf(bio_err, "Invalid engine \"%s\"\n", id); + ERR_print_errors(bio_err); + return NULL; + } + if (debug) + (void)ENGINE_ctrl(e, ENGINE_CTRL_SET_LOGSTREAM, 0, bio_err, 0); + if (!ENGINE_ctrl_cmd(e, "SET_USER_INTERFACE", 0, + (void *)get_ui_method(), 0, 1) + || !ENGINE_set_default(e, methods)) { + BIO_printf(bio_err, "Cannot use engine \"%s\"\n", ENGINE_get_id(e)); + ERR_print_errors(bio_err); + ENGINE_free(e); + return NULL; + } + + BIO_printf(bio_err, "Engine \"%s\" set.\n", ENGINE_get_id(e)); + } +#endif + return e; +} + +void release_engine(ENGINE *e) +{ +#ifndef OPENSSL_NO_ENGINE + /* Free our "structural" reference. */ + ENGINE_free(e); +#endif +} + +int init_engine(ENGINE *e) +{ + int rv = 1; + +#ifndef OPENSSL_NO_ENGINE + rv = ENGINE_init(e); +#endif + return rv; +} + +int finish_engine(ENGINE *e) +{ + int rv = 1; + +#ifndef OPENSSL_NO_ENGINE + rv = ENGINE_finish(e); +#endif + return rv; +} + +char *make_engine_uri(ENGINE *e, const char *key_id, const char *desc) +{ + char *new_uri = NULL; + +#ifndef OPENSSL_NO_ENGINE + if (e == NULL) { + BIO_printf(bio_err, "No engine specified for loading %s\n", desc); + } else if (key_id == NULL) { + BIO_printf(bio_err, "No engine key id specified for loading %s\n", desc); + } else { + const char *engineid = ENGINE_get_id(e); + size_t uri_sz = + sizeof(ENGINE_SCHEME_COLON) - 1 + + strlen(engineid) + + 1 /* : */ + + strlen(key_id) + + 1 /* \0 */ + ; + + new_uri = OPENSSL_malloc(uri_sz); + if (new_uri != NULL) { + OPENSSL_strlcpy(new_uri, ENGINE_SCHEME_COLON, uri_sz); + OPENSSL_strlcat(new_uri, engineid, uri_sz); + OPENSSL_strlcat(new_uri, ":", uri_sz); + OPENSSL_strlcat(new_uri, key_id, uri_sz); + } + } +#else + BIO_printf(bio_err, "Engines not supported for loading %s\n", desc); +#endif + return new_uri; +} + +int get_legacy_pkey_id(OSSL_LIB_CTX *libctx, const char *algname, ENGINE *e) +{ + const EVP_PKEY_ASN1_METHOD *ameth; + ENGINE *tmpeng = NULL; + int pkey_id = NID_undef; + + ERR_set_mark(); + ameth = EVP_PKEY_asn1_find_str(&tmpeng, algname, -1); + +#if !defined(OPENSSL_NO_ENGINE) + ENGINE_finish(tmpeng); + + if (ameth == NULL && e != NULL) + ameth = ENGINE_get_pkey_asn1_meth_str(e, algname, -1); + else +#endif + /* We're only interested if it comes from an ENGINE */ + if (tmpeng == NULL) + ameth = NULL; + + ERR_pop_to_mark(); + if (ameth == NULL) + return NID_undef; + + EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth); + + return pkey_id; +} + +const EVP_MD *get_digest_from_engine(const char *name) +{ +#ifndef OPENSSL_NO_ENGINE + ENGINE *eng; + + eng = ENGINE_get_digest_engine(OBJ_sn2nid(name)); + if (eng != NULL) { + ENGINE_finish(eng); + return EVP_get_digestbyname(name); + } +#endif + return NULL; +} + +const EVP_CIPHER *get_cipher_from_engine(const char *name) +{ +#ifndef OPENSSL_NO_ENGINE + ENGINE *eng; + + eng = ENGINE_get_cipher_engine(OBJ_sn2nid(name)); + if (eng != NULL) { + ENGINE_finish(eng); + return EVP_get_cipherbyname(name); + } +#endif + return NULL; +} diff --git a/apps/lib/engine_loader.c b/apps/lib/engine_loader.c new file mode 100644 index 000000000000..42775a89f361 --- /dev/null +++ b/apps/lib/engine_loader.c @@ -0,0 +1,203 @@ +/* + * Copyright 2018-2022 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 + */ + +/* + * Here is an STORE loader for ENGINE backed keys. It relies on deprecated + * functions, and therefore need to have deprecation warnings suppressed. + * This file is not compiled at all in a '--api=3 no-deprecated' configuration. + */ +#define OPENSSL_SUPPRESS_DEPRECATED + +#include "apps.h" + +#ifndef OPENSSL_NO_ENGINE + +# include <stdarg.h> +# include <string.h> +# include <openssl/engine.h> +# include <openssl/store.h> + +/* + * Support for legacy private engine keys via the 'org.openssl.engine:' scheme + * + * org.openssl.engine:{engineid}:{keyid} + * + * Note: we ONLY support ENGINE_load_private_key() and ENGINE_load_public_key() + * Note 2: This scheme has a precedent in code in PKIX-SSH. for exactly + * this sort of purpose. + */ + +/* Local definition of OSSL_STORE_LOADER_CTX */ +struct ossl_store_loader_ctx_st { + ENGINE *e; /* Structural reference */ + char *keyid; + int expected; + int loaded; /* 0 = key not loaded yet, 1 = key loaded */ +}; + +static OSSL_STORE_LOADER_CTX *OSSL_STORE_LOADER_CTX_new(ENGINE *e, char *keyid) +{ + OSSL_STORE_LOADER_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx)); + + if (ctx != NULL) { + ctx->e = e; + ctx->keyid = keyid; + } + return ctx; +} + +static void OSSL_STORE_LOADER_CTX_free(OSSL_STORE_LOADER_CTX *ctx) +{ + if (ctx != NULL) { + ENGINE_free(ctx->e); + OPENSSL_free(ctx->keyid); + OPENSSL_free(ctx); + } +} + +static OSSL_STORE_LOADER_CTX *engine_open(const OSSL_STORE_LOADER *loader, + const char *uri, + const UI_METHOD *ui_method, + void *ui_data) +{ + const char *p = uri, *q; + ENGINE *e = NULL; + char *keyid = NULL; + OSSL_STORE_LOADER_CTX *ctx = NULL; + + if (OPENSSL_strncasecmp(p, ENGINE_SCHEME_COLON, sizeof(ENGINE_SCHEME_COLON) - 1) + != 0) + return NULL; + p += sizeof(ENGINE_SCHEME_COLON) - 1; + + /* Look for engine ID */ + q = strchr(p, ':'); + if (q != NULL /* There is both an engine ID and a key ID */ + && p[0] != ':' /* The engine ID is at least one character */ + && q[1] != '\0') { /* The key ID is at least one character */ + char engineid[256]; + size_t engineid_l = q - p; + + strncpy(engineid, p, engineid_l); + engineid[engineid_l] = '\0'; + e = ENGINE_by_id(engineid); + + keyid = OPENSSL_strdup(q + 1); + } + + if (e != NULL && keyid != NULL) + ctx = OSSL_STORE_LOADER_CTX_new(e, keyid); + + if (ctx == NULL) { + OPENSSL_free(keyid); + ENGINE_free(e); + } + + return ctx; +} + +static int engine_expect(OSSL_STORE_LOADER_CTX *ctx, int expected) +{ + if (expected == 0 + || expected == OSSL_STORE_INFO_PUBKEY + || expected == OSSL_STORE_INFO_PKEY) { + ctx->expected = expected; + return 1; + } + return 0; +} + +static OSSL_STORE_INFO *engine_load(OSSL_STORE_LOADER_CTX *ctx, + const UI_METHOD *ui_method, void *ui_data) +{ + EVP_PKEY *pkey = NULL, *pubkey = NULL; + OSSL_STORE_INFO *info = NULL; + + if (ctx->loaded == 0) { + if (ENGINE_init(ctx->e)) { + if (ctx->expected == 0 + || ctx->expected == OSSL_STORE_INFO_PKEY) + pkey = + ENGINE_load_private_key(ctx->e, ctx->keyid, + (UI_METHOD *)ui_method, ui_data); + if ((pkey == NULL && ctx->expected == 0) + || ctx->expected == OSSL_STORE_INFO_PUBKEY) + pubkey = + ENGINE_load_public_key(ctx->e, ctx->keyid, + (UI_METHOD *)ui_method, ui_data); + ENGINE_finish(ctx->e); + } + } + + ctx->loaded = 1; + + if (pubkey != NULL) + info = OSSL_STORE_INFO_new_PUBKEY(pubkey); + else if (pkey != NULL) + info = OSSL_STORE_INFO_new_PKEY(pkey); + if (info == NULL) { + EVP_PKEY_free(pkey); + EVP_PKEY_free(pubkey); + } + return info; +} + +static int engine_eof(OSSL_STORE_LOADER_CTX *ctx) +{ + return ctx->loaded != 0; +} + +static int engine_error(OSSL_STORE_LOADER_CTX *ctx) +{ + return 0; +} + +static int engine_close(OSSL_STORE_LOADER_CTX *ctx) +{ + OSSL_STORE_LOADER_CTX_free(ctx); + return 1; +} + +int setup_engine_loader(void) +{ + OSSL_STORE_LOADER *loader = NULL; + + if ((loader = OSSL_STORE_LOADER_new(NULL, ENGINE_SCHEME)) == NULL + || !OSSL_STORE_LOADER_set_open(loader, engine_open) + || !OSSL_STORE_LOADER_set_expect(loader, engine_expect) + || !OSSL_STORE_LOADER_set_load(loader, engine_load) + || !OSSL_STORE_LOADER_set_eof(loader, engine_eof) + || !OSSL_STORE_LOADER_set_error(loader, engine_error) + || !OSSL_STORE_LOADER_set_close(loader, engine_close) + || !OSSL_STORE_register_loader(loader)) { + OSSL_STORE_LOADER_free(loader); + loader = NULL; + } + + return loader != NULL; +} + +void destroy_engine_loader(void) +{ + OSSL_STORE_LOADER *loader = OSSL_STORE_unregister_loader(ENGINE_SCHEME); + OSSL_STORE_LOADER_free(loader); +} + +#else /* !OPENSSL_NO_ENGINE */ + +int setup_engine_loader(void) +{ + return 0; +} + +void destroy_engine_loader(void) +{ +} + +#endif diff --git a/apps/lib/fmt.c b/apps/lib/fmt.c new file mode 100644 index 000000000000..af0e63b85b14 --- /dev/null +++ b/apps/lib/fmt.c @@ -0,0 +1,15 @@ +/* + * Copyright 2018-2020 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 "fmt.h" + +int FMT_istext(int format) +{ + return (format & B_FORMAT_TEXT) == B_FORMAT_TEXT; +} diff --git a/apps/lib/http_server.c b/apps/lib/http_server.c new file mode 100644 index 000000000000..33ae886d4a1c --- /dev/null +++ b/apps/lib/http_server.c @@ -0,0 +1,536 @@ +/* + * Copyright 1995-2022 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 + */ + +/* Very basic HTTP server */ + +#if !defined(_POSIX_C_SOURCE) && defined(OPENSSL_SYS_VMS) +/* + * On VMS, you need to define this to get the declaration of fileno(). The + * value 2 is to make sure no function defined in POSIX-2 is left undefined. + */ +# define _POSIX_C_SOURCE 2 +#endif + +#include <string.h> +#include <ctype.h> +#include "http_server.h" +#include "internal/sockets.h" +#include <openssl/err.h> +#include <openssl/rand.h> +#include "s_apps.h" + +#if defined(__TANDEM) +# if defined(OPENSSL_TANDEM_FLOSS) +# include <floss.h(floss_fork)> +# endif +#endif + +static int verbosity = LOG_INFO; + +#define HTTP_PREFIX "HTTP/" +#define HTTP_VERSION_PATT "1." /* allow 1.x */ +#define HTTP_PREFIX_VERSION HTTP_PREFIX""HTTP_VERSION_PATT +#define HTTP_1_0 HTTP_PREFIX_VERSION"0" /* "HTTP/1.0" */ + +#ifdef HTTP_DAEMON + +int multi = 0; /* run multiple responder processes */ +int acfd = (int) INVALID_SOCKET; + +static int print_syslog(const char *str, size_t len, void *levPtr) +{ + int level = *(int *)levPtr; + int ilen = len > MAXERRLEN ? MAXERRLEN : len; + + syslog(level, "%.*s", ilen, str); + + return ilen; +} +#endif + +void log_message(const char *prog, int level, const char *fmt, ...) +{ + va_list ap; + + if (verbosity < level) + return; + + va_start(ap, fmt); +#ifdef HTTP_DAEMON + if (multi) { + char buf[1024]; + + if (vsnprintf(buf, sizeof(buf), fmt, ap) > 0) + syslog(level, "%s", buf); + if (level <= LOG_ERR) + ERR_print_errors_cb(print_syslog, &level); + } else +#endif + { + BIO_printf(bio_err, "%s: ", prog); + BIO_vprintf(bio_err, fmt, ap); + BIO_printf(bio_err, "\n"); + (void)BIO_flush(bio_err); + } + va_end(ap); +} + +#ifdef HTTP_DAEMON +void socket_timeout(int signum) +{ + if (acfd != (int)INVALID_SOCKET) + (void)shutdown(acfd, SHUT_RD); +} + +static void killall(int ret, pid_t *kidpids) +{ + int i; + + for (i = 0; i < multi; ++i) + if (kidpids[i] != 0) + (void)kill(kidpids[i], SIGTERM); + OPENSSL_free(kidpids); + ossl_sleep(1000); + exit(ret); +} + +static int termsig = 0; + +static void noteterm(int sig) +{ + termsig = sig; +} + +/* + * Loop spawning up to `multi` child processes, only child processes return + * from this function. The parent process loops until receiving a termination + * signal, kills extant children and exits without returning. + */ +void spawn_loop(const char *prog) +{ + pid_t *kidpids = NULL; + int status; + int procs = 0; + int i; + + openlog(prog, LOG_PID, LOG_DAEMON); + + if (setpgid(0, 0)) { + syslog(LOG_ERR, "fatal: error detaching from parent process group: %s", + strerror(errno)); + exit(1); + } + kidpids = app_malloc(multi * sizeof(*kidpids), "child PID array"); + for (i = 0; i < multi; ++i) + kidpids[i] = 0; + + signal(SIGINT, noteterm); + signal(SIGTERM, noteterm); + + while (termsig == 0) { + pid_t fpid; + + /* + * Wait for a child to replace when we're at the limit. + * Slow down if a child exited abnormally or waitpid() < 0 + */ + while (termsig == 0 && procs >= multi) { + if ((fpid = waitpid(-1, &status, 0)) > 0) { + for (i = 0; i < procs; ++i) { + if (kidpids[i] == fpid) { + kidpids[i] = 0; + --procs; + break; + } + } + if (i >= multi) { + syslog(LOG_ERR, "fatal: internal error: " + "no matching child slot for pid: %ld", + (long) fpid); + killall(1, kidpids); + } + if (status != 0) { + if (WIFEXITED(status)) + syslog(LOG_WARNING, "child process: %ld, exit status: %d", + (long)fpid, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + syslog(LOG_WARNING, "child process: %ld, term signal %d%s", + (long)fpid, WTERMSIG(status), +# ifdef WCOREDUMP + WCOREDUMP(status) ? " (core dumped)" : +# endif + ""); + ossl_sleep(1000); + } + break; + } else if (errno != EINTR) { + syslog(LOG_ERR, "fatal: waitpid(): %s", strerror(errno)); + killall(1, kidpids); + } + } + if (termsig) + break; + + switch (fpid = fork()) { + case -1: /* error */ + /* System critically low on memory, pause and try again later */ + ossl_sleep(30000); + break; + case 0: /* child */ + OPENSSL_free(kidpids); + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + if (termsig) + _exit(0); + if (RAND_poll() <= 0) { + syslog(LOG_ERR, "fatal: RAND_poll() failed"); + _exit(1); + } + return; + default: /* parent */ + for (i = 0; i < multi; ++i) { + if (kidpids[i] == 0) { + kidpids[i] = fpid; + procs++; + break; + } + } + if (i >= multi) { + syslog(LOG_ERR, "fatal: internal error: no free child slots"); + killall(1, kidpids); + } + break; + } + } + + /* The loop above can only break on termsig */ + syslog(LOG_INFO, "terminating on signal: %d", termsig); + killall(0, kidpids); +} +#endif + +#ifndef OPENSSL_NO_SOCK +BIO *http_server_init_bio(const char *prog, const char *port) +{ + BIO *acbio = NULL, *bufbio; + int asock; + char name[40]; + + snprintf(name, sizeof(name), "[::]:%s", port); /* port may be "0" */ + bufbio = BIO_new(BIO_f_buffer()); + if (bufbio == NULL) + goto err; + acbio = BIO_new(BIO_s_accept()); + if (acbio == NULL + || BIO_set_accept_ip_family(acbio, BIO_FAMILY_IPANY) <= 0 /* IPv4/6 */ + || BIO_set_bind_mode(acbio, BIO_BIND_REUSEADDR) <= 0 + || BIO_set_accept_name(acbio, name) <= 0) { + log_message(prog, LOG_ERR, "Error setting up accept BIO"); + goto err; + } + + BIO_set_accept_bios(acbio, bufbio); + bufbio = NULL; + if (BIO_do_accept(acbio) <= 0) { + log_message(prog, LOG_ERR, "Error starting accept"); + goto err; + } + + /* Report back what address and port are used */ + BIO_get_fd(acbio, &asock); + if (!report_server_accept(bio_out, asock, 1, 1)) { + log_message(prog, LOG_ERR, "Error printing ACCEPT string"); + goto err; + } + + return acbio; + + err: + BIO_free_all(acbio); + BIO_free(bufbio); + return NULL; +} + +/* + * Decode %xx URL-decoding in-place. Ignores malformed sequences. + */ +static int urldecode(char *p) +{ + unsigned char *out = (unsigned char *)p; + unsigned char *save = out; + + for (; *p; p++) { + if (*p != '%') { + *out++ = *p; + } else if (isxdigit(_UC(p[1])) && isxdigit(_UC(p[2]))) { + /* Don't check, can't fail because of ixdigit() call. */ + *out++ = (OPENSSL_hexchar2int(p[1]) << 4) + | OPENSSL_hexchar2int(p[2]); + p += 2; + } else { + return -1; + } + } + *out = '\0'; + return (int)(out - save); +} + +/* if *pcbio != NULL, continue given connected session, else accept new */ +/* if found_keep_alive != NULL, return this way connection persistence state */ +int http_server_get_asn1_req(const ASN1_ITEM *it, ASN1_VALUE **preq, + char **ppath, BIO **pcbio, BIO *acbio, + int *found_keep_alive, + const char *prog, const char *port, + int accept_get, int timeout) +{ + BIO *cbio = *pcbio, *getbio = NULL, *b64 = NULL; + int len; + char reqbuf[2048], inbuf[2048]; + char *meth, *url, *end; + ASN1_VALUE *req; + int ret = 0; + + *preq = NULL; + if (ppath != NULL) + *ppath = NULL; + + if (cbio == NULL) { + log_message(prog, LOG_DEBUG, + "Awaiting new connection on port %s...", port); + if (BIO_do_accept(acbio) <= 0) + /* Connection loss before accept() is routine, ignore silently */ + return ret; + + *pcbio = cbio = BIO_pop(acbio); + } else { + log_message(prog, LOG_DEBUG, "Awaiting next request..."); + } + if (cbio == NULL) { + /* Cannot call http_server_send_status(cbio, ...) */ + ret = -1; + goto out; + } + +# ifdef HTTP_DAEMON + if (timeout > 0) { + (void)BIO_get_fd(cbio, &acfd); + alarm(timeout); + } +# endif + + /* Read the request line. */ + len = BIO_gets(cbio, reqbuf, sizeof(reqbuf)); + if (len == 0) + return ret; + ret = 1; + if (len < 0) { + log_message(prog, LOG_WARNING, "Request line read error"); + (void)http_server_send_status(cbio, 400, "Bad Request"); + goto out; + } + if ((end = strchr(reqbuf, '\r')) != NULL + || (end = strchr(reqbuf, '\n')) != NULL) + *end = '\0'; + log_message(prog, LOG_INFO, "Received request, 1st line: %s", reqbuf); + + meth = reqbuf; + url = meth + 3; + if ((accept_get && strncmp(meth, "GET ", 4) == 0) + || (url++, strncmp(meth, "POST ", 5) == 0)) { + static const char http_version_str[] = " "HTTP_PREFIX_VERSION; + static const size_t http_version_str_len = sizeof(http_version_str) - 1; + + /* Expecting (GET|POST) {sp} /URL {sp} HTTP/1.x */ + *(url++) = '\0'; + while (*url == ' ') + url++; + if (*url != '/') { + log_message(prog, LOG_WARNING, + "Invalid %s -- URL does not begin with '/': %s", + meth, url); + (void)http_server_send_status(cbio, 400, "Bad Request"); + goto out; + } + url++; + + /* Splice off the HTTP version identifier. */ + for (end = url; *end != '\0'; end++) + if (*end == ' ') + break; + if (strncmp(end, http_version_str, http_version_str_len) != 0) { + log_message(prog, LOG_WARNING, + "Invalid %s -- bad HTTP/version string: %s", + meth, end + 1); + (void)http_server_send_status(cbio, 400, "Bad Request"); + goto out; + } + *end = '\0'; + /* above HTTP 1.0, connection persistence is the default */ + if (found_keep_alive != NULL) + *found_keep_alive = end[http_version_str_len] > '0'; + + /*- + * Skip "GET / HTTP..." requests often used by load-balancers. + * 'url' was incremented above to point to the first byte *after* + * the leading slash, so in case 'GET / ' it is now an empty string. + */ + if (strlen(meth) == 3 && url[0] == '\0') { + (void)http_server_send_status(cbio, 200, "OK"); + goto out; + } + + len = urldecode(url); + if (len < 0) { + log_message(prog, LOG_WARNING, + "Invalid %s request -- bad URL encoding: %s", + meth, url); + (void)http_server_send_status(cbio, 400, "Bad Request"); + goto out; + } + if (strlen(meth) == 3) { /* GET */ + if ((getbio = BIO_new_mem_buf(url, len)) == NULL + || (b64 = BIO_new(BIO_f_base64())) == NULL) { + log_message(prog, LOG_ERR, + "Could not allocate base64 bio with size = %d", + len); + goto fatal; + } + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + getbio = BIO_push(b64, getbio); + } + } else { + log_message(prog, LOG_WARNING, + "HTTP request does not begin with %sPOST: %s", + accept_get ? "GET or " : "", reqbuf); + (void)http_server_send_status(cbio, 400, "Bad Request"); + goto out; + } + + /* chop any further/duplicate leading or trailing '/' */ + while (*url == '/') + url++; + while (end >= url + 2 && end[-2] == '/' && end[-1] == '/') + end--; + *end = '\0'; + + /* Read and skip past the headers. */ + for (;;) { + char *key, *value, *line_end = NULL; + + len = BIO_gets(cbio, inbuf, sizeof(inbuf)); + if (len <= 0) { + log_message(prog, LOG_WARNING, "Error reading HTTP header"); + (void)http_server_send_status(cbio, 400, "Bad Request"); + goto out; + } + + if (inbuf[0] == '\r' || inbuf[0] == '\n') + break; + + key = inbuf; + value = strchr(key, ':'); + if (value == NULL) { + log_message(prog, LOG_WARNING, + "Error parsing HTTP header: missing ':'"); + (void)http_server_send_status(cbio, 400, "Bad Request"); + goto out; + } + *(value++) = '\0'; + while (*value == ' ') + value++; + line_end = strchr(value, '\r'); + if (line_end == NULL) { + line_end = strchr(value, '\n'); + if (line_end == NULL) { + log_message(prog, LOG_WARNING, + "Error parsing HTTP header: missing end of line"); + (void)http_server_send_status(cbio, 400, "Bad Request"); + goto out; + } + } + *line_end = '\0'; + /* https://tools.ietf.org/html/rfc7230#section-6.3 Persistence */ + if (found_keep_alive != NULL + && OPENSSL_strcasecmp(key, "Connection") == 0) { + if (OPENSSL_strcasecmp(value, "keep-alive") == 0) + *found_keep_alive = 1; + else if (OPENSSL_strcasecmp(value, "close") == 0) + *found_keep_alive = 0; + } + } + +# ifdef HTTP_DAEMON + /* Clear alarm before we close the client socket */ + alarm(0); + timeout = 0; +# endif + + /* Try to read and parse request */ + req = ASN1_item_d2i_bio(it, getbio != NULL ? getbio : cbio, NULL); + if (req == NULL) { + log_message(prog, LOG_WARNING, + "Error parsing DER-encoded request content"); + (void)http_server_send_status(cbio, 400, "Bad Request"); + } else if (ppath != NULL && (*ppath = OPENSSL_strdup(url)) == NULL) { + log_message(prog, LOG_ERR, + "Out of memory allocating %zu bytes", strlen(url) + 1); + ASN1_item_free(req, it); + goto fatal; + } + + *preq = req; + + out: + BIO_free_all(getbio); +# ifdef HTTP_DAEMON + if (timeout > 0) + alarm(0); + acfd = (int)INVALID_SOCKET; +# endif + return ret; + + fatal: + (void)http_server_send_status(cbio, 500, "Internal Server Error"); + if (ppath != NULL) { + OPENSSL_free(*ppath); + *ppath = NULL; + } + BIO_free_all(cbio); + *pcbio = NULL; + ret = -1; + goto out; +} + +/* assumes that cbio does not do an encoding that changes the output length */ +int http_server_send_asn1_resp(BIO *cbio, int keep_alive, + const char *content_type, + const ASN1_ITEM *it, const ASN1_VALUE *resp) +{ + int ret = BIO_printf(cbio, HTTP_1_0" 200 OK\r\n%s" + "Content-type: %s\r\n" + "Content-Length: %d\r\n\r\n", + keep_alive ? "Connection: keep-alive\r\n" : "", + content_type, + ASN1_item_i2d(resp, NULL, it)) > 0 + && ASN1_item_i2d_bio(it, cbio, resp) > 0; + + (void)BIO_flush(cbio); + return ret; +} + +int http_server_send_status(BIO *cbio, int status, const char *reason) +{ + int ret = BIO_printf(cbio, HTTP_1_0" %d %s\r\n\r\n", + /* This implicitly cancels keep-alive */ + status, reason) > 0; + + (void)BIO_flush(cbio); + return ret; +} +#endif diff --git a/apps/lib/names.c b/apps/lib/names.c new file mode 100644 index 000000000000..4788ae84b915 --- /dev/null +++ b/apps/lib/names.c @@ -0,0 +1,45 @@ +/* + * Copyright 2019-2022 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 <string.h> +#include <openssl/bio.h> +#include <openssl/safestack.h> +#include "names.h" +#include "openssl/crypto.h" + +int name_cmp(const char * const *a, const char * const *b) +{ + return OPENSSL_strcasecmp(*a, *b); +} + +void collect_names(const char *name, void *vdata) +{ + STACK_OF(OPENSSL_CSTRING) *names = vdata; + + sk_OPENSSL_CSTRING_push(names, name); +} + +void print_names(BIO *out, STACK_OF(OPENSSL_CSTRING) *names) +{ + int i = sk_OPENSSL_CSTRING_num(names); + int j; + + sk_OPENSSL_CSTRING_sort(names); + if (i > 1) + BIO_printf(out, "{ "); + for (j = 0; j < i; j++) { + const char *name = sk_OPENSSL_CSTRING_value(names, j); + + if (j > 0) + BIO_printf(out, ", "); + BIO_printf(out, "%s", name); + } + if (i > 1) + BIO_printf(out, " }"); +} diff --git a/apps/lib/opt.c b/apps/lib/opt.c new file mode 100644 index 000000000000..88db9ad6947b --- /dev/null +++ b/apps/lib/opt.c @@ -0,0 +1,1203 @@ +/* + * Copyright 2015-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 + */ + +/* + * This file is also used by the test suite. Do not #include "apps.h". + */ +#include "opt.h" +#include "fmt.h" +#include "app_libctx.h" +#include "internal/nelem.h" +#include "internal/numbers.h" +#include <string.h> +#if !defined(OPENSSL_SYS_MSDOS) +# include <unistd.h> +#endif + +#include <stdlib.h> +#include <errno.h> +#include <ctype.h> +#include <limits.h> +#include <openssl/err.h> +#include <openssl/bio.h> +#include <openssl/x509v3.h> + +#define MAX_OPT_HELP_WIDTH 30 +const char OPT_HELP_STR[] = "-H"; +const char OPT_MORE_STR[] = "-M"; +const char OPT_SECTION_STR[] = "-S"; +const char OPT_PARAM_STR[] = "-P"; + +/* Our state */ +static char **argv; +static int argc; +static int opt_index; +static char *arg; +static char *flag; +static char *dunno; +static const OPTIONS *unknown; +static const OPTIONS *opts; +static char prog[40]; + +/* + * Return the simple name of the program; removing various platform gunk. + */ +#if defined(OPENSSL_SYS_WIN32) + +const char *opt_path_end(const char *filename) +{ + const char *p; + + /* find the last '/', '\' or ':' */ + for (p = filename + strlen(filename); --p > filename; ) + if (*p == '/' || *p == '\\' || *p == ':') { + p++; + break; + } + return p; +} + +char *opt_progname(const char *argv0) +{ + size_t i, n; + const char *p; + char *q; + + p = opt_path_end(argv0); + + /* Strip off trailing nonsense. */ + n = strlen(p); + if (n > 4 && + (strcmp(&p[n - 4], ".exe") == 0 || strcmp(&p[n - 4], ".EXE") == 0)) + n -= 4; + + /* Copy over the name, in lowercase. */ + if (n > sizeof(prog) - 1) + n = sizeof(prog) - 1; + for (q = prog, i = 0; i < n; i++, p++) + *q++ = tolower((unsigned char)*p); + *q = '\0'; + return prog; +} + +#elif defined(OPENSSL_SYS_VMS) + +const char *opt_path_end(const char *filename) +{ + const char *p; + + /* Find last special character sys:[foo.bar]openssl */ + for (p = filename + strlen(filename); --p > filename;) + if (*p == ':' || *p == ']' || *p == '>') { + p++; + break; + } + return p; +} + +char *opt_progname(const char *argv0) +{ + const char *p, *q; + + /* Find last special character sys:[foo.bar]openssl */ + p = opt_path_end(argv0); + q = strrchr(p, '.'); + if (prog != p) + strncpy(prog, p, sizeof(prog) - 1); + prog[sizeof(prog) - 1] = '\0'; + if (q != NULL && q - p < sizeof(prog)) + prog[q - p] = '\0'; + return prog; +} + +#else + +const char *opt_path_end(const char *filename) +{ + const char *p; + + /* Could use strchr, but this is like the ones above. */ + for (p = filename + strlen(filename); --p > filename;) + if (*p == '/') { + p++; + break; + } + return p; +} + +char *opt_progname(const char *argv0) +{ + const char *p; + + p = opt_path_end(argv0); + if (prog != p) + strncpy(prog, p, sizeof(prog) - 1); + prog[sizeof(prog) - 1] = '\0'; + return prog; +} +#endif + +char *opt_appname(const char *argv0) +{ + size_t len = strlen(prog); + + if (argv0 != NULL) + BIO_snprintf(prog + len, sizeof(prog) - len - 1, " %s", argv0); + return prog; +} + +char *opt_getprog(void) +{ + return prog; +} + +/* Set up the arg parsing. */ +char *opt_init(int ac, char **av, const OPTIONS *o) +{ + /* Store state. */ + argc = ac; + argv = av; + opt_begin(); + opts = o; + unknown = NULL; + + /* Make sure prog name is set for usage output */ + (void)opt_progname(argv[0]); + + /* Check all options up until the PARAM marker (if present) */ + for (; o->name != NULL && o->name != OPT_PARAM_STR; ++o) { +#ifndef NDEBUG + const OPTIONS *next; + int duplicated, i; +#endif + + if (o->name == OPT_HELP_STR + || o->name == OPT_MORE_STR + || o->name == OPT_SECTION_STR) + continue; +#ifndef NDEBUG + i = o->valtype; + + /* Make sure options are legit. */ + OPENSSL_assert(o->name[0] != '-'); + if (o->valtype == '.') + OPENSSL_assert(o->retval == OPT_PARAM); + else + OPENSSL_assert(o->retval == OPT_DUP || o->retval > OPT_PARAM); + switch (i) { + case 0: case '-': case '.': + case '/': case '<': case '>': case 'E': case 'F': + case 'M': case 'U': case 'f': case 'l': case 'n': case 'p': case 's': + case 'u': case 'c': case ':': case 'N': + break; + default: + OPENSSL_assert(0); + } + + /* Make sure there are no duplicates. */ + for (next = o + 1; next->name; ++next) { + /* + * Some compilers inline strcmp and the assert string is too long. + */ + duplicated = next->retval != OPT_DUP + && strcmp(o->name, next->name) == 0; + if (duplicated) { + opt_printf_stderr("%s: Internal error: duplicate option %s\n", + prog, o->name); + OPENSSL_assert(!duplicated); + } + } +#endif + if (o->name[0] == '\0') { + OPENSSL_assert(unknown == NULL); + unknown = o; + OPENSSL_assert(unknown->valtype == 0 || unknown->valtype == '-'); + } + } + return prog; +} + +static OPT_PAIR formats[] = { + {"PEM/DER", OPT_FMT_PEMDER}, + {"pkcs12", OPT_FMT_PKCS12}, + {"smime", OPT_FMT_SMIME}, + {"engine", OPT_FMT_ENGINE}, + {"msblob", OPT_FMT_MSBLOB}, + {"nss", OPT_FMT_NSS}, + {"text", OPT_FMT_TEXT}, + {"http", OPT_FMT_HTTP}, + {"pvk", OPT_FMT_PVK}, + {NULL} +}; + +/* Print an error message about a failed format parse. */ +static int opt_format_error(const char *s, unsigned long flags) +{ + OPT_PAIR *ap; + + if (flags == OPT_FMT_PEMDER) { + opt_printf_stderr("%s: Bad format \"%s\"; must be pem or der\n", + prog, s); + } else { + opt_printf_stderr("%s: Bad format \"%s\"; must be one of:\n", + prog, s); + for (ap = formats; ap->name; ap++) + if (flags & ap->retval) + opt_printf_stderr(" %s\n", ap->name); + } + return 0; +} + +/* Parse a format string, put it into *result; return 0 on failure, else 1. */ +int opt_format(const char *s, unsigned long flags, int *result) +{ + switch (*s) { + default: + opt_printf_stderr("%s: Bad format \"%s\"\n", prog, s); + return 0; + case 'D': + case 'd': + if ((flags & OPT_FMT_PEMDER) == 0) + return opt_format_error(s, flags); + *result = FORMAT_ASN1; + break; + case 'T': + case 't': + if ((flags & OPT_FMT_TEXT) == 0) + return opt_format_error(s, flags); + *result = FORMAT_TEXT; + break; + case 'N': + case 'n': + if ((flags & OPT_FMT_NSS) == 0) + return opt_format_error(s, flags); + if (strcmp(s, "NSS") != 0 && strcmp(s, "nss") != 0) + return opt_format_error(s, flags); + *result = FORMAT_NSS; + break; + case 'S': + case 's': + if ((flags & OPT_FMT_SMIME) == 0) + return opt_format_error(s, flags); + *result = FORMAT_SMIME; + break; + case 'M': + case 'm': + if ((flags & OPT_FMT_MSBLOB) == 0) + return opt_format_error(s, flags); + *result = FORMAT_MSBLOB; + break; + case 'E': + case 'e': + if ((flags & OPT_FMT_ENGINE) == 0) + return opt_format_error(s, flags); + *result = FORMAT_ENGINE; + break; + case 'H': + case 'h': + if ((flags & OPT_FMT_HTTP) == 0) + return opt_format_error(s, flags); + *result = FORMAT_HTTP; + break; + case '1': + if ((flags & OPT_FMT_PKCS12) == 0) + return opt_format_error(s, flags); + *result = FORMAT_PKCS12; + break; + case 'P': + case 'p': + if (s[1] == '\0' || strcmp(s, "PEM") == 0 || strcmp(s, "pem") == 0) { + if ((flags & OPT_FMT_PEMDER) == 0) + return opt_format_error(s, flags); + *result = FORMAT_PEM; + } else if (strcmp(s, "PVK") == 0 || strcmp(s, "pvk") == 0) { + if ((flags & OPT_FMT_PVK) == 0) + return opt_format_error(s, flags); + *result = FORMAT_PVK; + } else if (strcmp(s, "P12") == 0 || strcmp(s, "p12") == 0 + || strcmp(s, "PKCS12") == 0 || strcmp(s, "pkcs12") == 0) { + if ((flags & OPT_FMT_PKCS12) == 0) + return opt_format_error(s, flags); + *result = FORMAT_PKCS12; + } else { + opt_printf_stderr("%s: Bad format \"%s\"\n", prog, s); + return 0; + } + break; + } + return 1; +} + +/* Return string representing the given format. */ +static const char *format2str(int format) +{ + switch (format) { + default: + return "(undefined)"; + case FORMAT_PEM: + return "PEM"; + case FORMAT_ASN1: + return "DER"; + case FORMAT_TEXT: + return "TEXT"; + case FORMAT_NSS: + return "NSS"; + case FORMAT_SMIME: + return "SMIME"; + case FORMAT_MSBLOB: + return "MSBLOB"; + case FORMAT_ENGINE: + return "ENGINE"; + case FORMAT_HTTP: + return "HTTP"; + case FORMAT_PKCS12: + return "P12"; + case FORMAT_PVK: + return "PVK"; + } +} + +/* Print an error message about unsuitable/unsupported format requested. */ +void print_format_error(int format, unsigned long flags) +{ + (void)opt_format_error(format2str(format), flags); +} + +/* + * Parse a cipher name, put it in *cipherp after freeing what was there, if + * cipherp is not NULL. Return 0 on failure, else 1. + */ +int opt_cipher_silent(const char *name, EVP_CIPHER **cipherp) +{ + EVP_CIPHER *c; + + ERR_set_mark(); + if ((c = EVP_CIPHER_fetch(app_get0_libctx(), name, + app_get0_propq())) != NULL + || (opt_legacy_okay() + && (c = (EVP_CIPHER *)EVP_get_cipherbyname(name)) != NULL)) { + ERR_pop_to_mark(); + if (cipherp != NULL) { + EVP_CIPHER_free(*cipherp); + *cipherp = c; + } else { + EVP_CIPHER_free(c); + } + return 1; + } + ERR_clear_last_mark(); + return 0; +} + +int opt_cipher_any(const char *name, EVP_CIPHER **cipherp) +{ + int ret; + + if ((ret = opt_cipher_silent(name, cipherp)) == 0) + opt_printf_stderr("%s: Unknown cipher: %s\n", prog, name); + return ret; +} + +int opt_cipher(const char *name, EVP_CIPHER **cipherp) +{ + int mode, ret = 0; + unsigned long int flags; + EVP_CIPHER *c = NULL; + + if (opt_cipher_any(name, &c)) { + mode = EVP_CIPHER_get_mode(c); + flags = EVP_CIPHER_get_flags(c); + if (mode == EVP_CIPH_XTS_MODE) { + opt_printf_stderr("%s XTS ciphers not supported\n", prog); + } else if ((flags & EVP_CIPH_FLAG_AEAD_CIPHER) != 0) { + opt_printf_stderr("%s: AEAD ciphers not supported\n", prog); + } else { + ret = 1; + if (cipherp != NULL) + *cipherp = c; + } + } + return ret; +} + +/* + * Parse message digest name, put it in *EVP_MD; return 0 on failure, else 1. + */ +int opt_md_silent(const char *name, EVP_MD **mdp) +{ + EVP_MD *md; + + ERR_set_mark(); + if ((md = EVP_MD_fetch(app_get0_libctx(), name, app_get0_propq())) != NULL + || (opt_legacy_okay() + && (md = (EVP_MD *)EVP_get_digestbyname(name)) != NULL)) { + ERR_pop_to_mark(); + if (mdp != NULL) { + EVP_MD_free(*mdp); + *mdp = md; + } else { + EVP_MD_free(md); + } + return 1; + } + ERR_clear_last_mark(); + return 0; +} + +int opt_md(const char *name, EVP_MD **mdp) +{ + int ret; + + if ((ret = opt_md_silent(name, mdp)) == 0) + opt_printf_stderr("%s: Unknown option or message digest: %s\n", prog, + name != NULL ? name : "\"\""); + return ret; +} + +/* Look through a list of name/value pairs. */ +int opt_pair(const char *name, const OPT_PAIR* pairs, int *result) +{ + const OPT_PAIR *pp; + + for (pp = pairs; pp->name; pp++) + if (strcmp(pp->name, name) == 0) { + *result = pp->retval; + return 1; + } + opt_printf_stderr("%s: Value must be one of:\n", prog); + for (pp = pairs; pp->name; pp++) + opt_printf_stderr("\t%s\n", pp->name); + return 0; +} + +/* Look through a list of valid names */ +int opt_string(const char *name, const char **options) +{ + const char **p; + + for (p = options; *p != NULL; p++) + if (strcmp(*p, name) == 0) + return 1; + opt_printf_stderr("%s: Value must be one of:\n", prog); + for (p = options; *p != NULL; p++) + opt_printf_stderr("\t%s\n", *p); + return 0; +} + +/* Parse an int, put it into *result; return 0 on failure, else 1. */ +int opt_int(const char *value, int *result) +{ + long l; + + if (!opt_long(value, &l)) + return 0; + *result = (int)l; + if (*result != l) { + opt_printf_stderr("%s: Value \"%s\" outside integer range\n", + prog, value); + return 0; + } + return 1; +} + +/* Parse and return an integer, assuming range has been checked before. */ +int opt_int_arg(void) +{ + int result = -1; + + (void)opt_int(arg, &result); + return result; +} + +static void opt_number_error(const char *v) +{ + size_t i = 0; + struct strstr_pair_st { + char *prefix; + char *name; + } b[] = { + {"0x", "a hexadecimal"}, + {"0X", "a hexadecimal"}, + {"0", "an octal"} + }; + + for (i = 0; i < OSSL_NELEM(b); i++) { + if (strncmp(v, b[i].prefix, strlen(b[i].prefix)) == 0) { + opt_printf_stderr("%s: Can't parse \"%s\" as %s number\n", + prog, v, b[i].name); + return; + } + } + opt_printf_stderr("%s: Can't parse \"%s\" as a number\n", prog, v); + return; +} + +/* Parse a long, put it into *result; return 0 on failure, else 1. */ +int opt_long(const char *value, long *result) +{ + int oerrno = errno; + long l; + char *endp; + + errno = 0; + l = strtol(value, &endp, 0); + if (*endp + || endp == value + || ((l == LONG_MAX || l == LONG_MIN) && errno == ERANGE) + || (l == 0 && errno != 0)) { + opt_number_error(value); + errno = oerrno; + return 0; + } + *result = l; + errno = oerrno; + return 1; +} + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L && \ + defined(INTMAX_MAX) && defined(UINTMAX_MAX) && \ + !defined(OPENSSL_NO_INTTYPES_H) + +/* Parse an intmax_t, put it into *result; return 0 on failure, else 1. */ +int opt_intmax(const char *value, ossl_intmax_t *result) +{ + int oerrno = errno; + intmax_t m; + char *endp; + + errno = 0; + m = strtoimax(value, &endp, 0); + if (*endp + || endp == value + || ((m == INTMAX_MAX || m == INTMAX_MIN) + && errno == ERANGE) + || (m == 0 && errno != 0)) { + opt_number_error(value); + errno = oerrno; + return 0; + } + /* Ensure that the value in |m| is never too big for |*result| */ + if (sizeof(m) > sizeof(*result) + && (m < OSSL_INTMAX_MIN || m > OSSL_INTMAX_MAX)) { + opt_number_error(value); + return 0; + } + *result = (ossl_intmax_t)m; + errno = oerrno; + return 1; +} + +/* Parse a uintmax_t, put it into *result; return 0 on failure, else 1. */ +int opt_uintmax(const char *value, ossl_uintmax_t *result) +{ + int oerrno = errno; + uintmax_t m; + char *endp; + + errno = 0; + m = strtoumax(value, &endp, 0); + if (*endp + || endp == value + || (m == UINTMAX_MAX && errno == ERANGE) + || (m == 0 && errno != 0)) { + opt_number_error(value); + errno = oerrno; + return 0; + } + /* Ensure that the value in |m| is never too big for |*result| */ + if (sizeof(m) > sizeof(*result) + && m > OSSL_UINTMAX_MAX) { + opt_number_error(value); + return 0; + } + *result = (ossl_uintmax_t)m; + errno = oerrno; + return 1; +} +#else +/* Fallback implementations based on long */ +int opt_intmax(const char *value, ossl_intmax_t *result) +{ + long m; + int ret; + + if ((ret = opt_long(value, &m))) + *result = m; + return ret; +} + +int opt_uintmax(const char *value, ossl_uintmax_t *result) +{ + unsigned long m; + int ret; + + if ((ret = opt_ulong(value, &m))) + *result = m; + return ret; +} +#endif + +/* + * Parse an unsigned long, put it into *result; return 0 on failure, else 1. + */ +int opt_ulong(const char *value, unsigned long *result) +{ + int oerrno = errno; + char *endptr; + unsigned long l; + + errno = 0; + l = strtoul(value, &endptr, 0); + if (*endptr + || endptr == value + || ((l == ULONG_MAX) && errno == ERANGE) + || (l == 0 && errno != 0)) { + opt_number_error(value); + errno = oerrno; + return 0; + } + *result = l; + errno = oerrno; + return 1; +} + +/* + * We pass opt as an int but cast it to "enum range" so that all the + * items in the OPT_V_ENUM enumeration are caught; this makes -Wswitch + * in gcc do the right thing. + */ +enum range { OPT_V_ENUM }; + +int opt_verify(int opt, X509_VERIFY_PARAM *vpm) +{ + int i; + ossl_intmax_t t = 0; + ASN1_OBJECT *otmp; + X509_PURPOSE *xptmp; + const X509_VERIFY_PARAM *vtmp; + + OPENSSL_assert(vpm != NULL); + OPENSSL_assert(opt > OPT_V__FIRST); + OPENSSL_assert(opt < OPT_V__LAST); + + switch ((enum range)opt) { + case OPT_V__FIRST: + case OPT_V__LAST: + return 0; + case OPT_V_POLICY: + otmp = OBJ_txt2obj(opt_arg(), 0); + if (otmp == NULL) { + opt_printf_stderr("%s: Invalid Policy %s\n", prog, opt_arg()); + return 0; + } + if (!X509_VERIFY_PARAM_add0_policy(vpm, otmp)) { + ASN1_OBJECT_free(otmp); + opt_printf_stderr("%s: Internal error adding Policy %s\n", + prog, opt_arg()); + return 0; + } + break; + case OPT_V_PURPOSE: + /* purpose name -> purpose index */ + i = X509_PURPOSE_get_by_sname(opt_arg()); + if (i < 0) { + opt_printf_stderr("%s: Invalid purpose %s\n", prog, opt_arg()); + return 0; + } + + /* purpose index -> purpose object */ + xptmp = X509_PURPOSE_get0(i); + + /* purpose object -> purpose value */ + i = X509_PURPOSE_get_id(xptmp); + + if (!X509_VERIFY_PARAM_set_purpose(vpm, i)) { + opt_printf_stderr("%s: Internal error setting purpose %s\n", + prog, opt_arg()); + return 0; + } + break; + case OPT_V_VERIFY_NAME: + vtmp = X509_VERIFY_PARAM_lookup(opt_arg()); + if (vtmp == NULL) { + opt_printf_stderr("%s: Invalid verify name %s\n", + prog, opt_arg()); + return 0; + } + X509_VERIFY_PARAM_set1(vpm, vtmp); + break; + case OPT_V_VERIFY_DEPTH: + i = atoi(opt_arg()); + if (i >= 0) + X509_VERIFY_PARAM_set_depth(vpm, i); + break; + case OPT_V_VERIFY_AUTH_LEVEL: + i = atoi(opt_arg()); + if (i >= 0) + X509_VERIFY_PARAM_set_auth_level(vpm, i); + break; + case OPT_V_ATTIME: + if (!opt_intmax(opt_arg(), &t)) + return 0; + if (t != (time_t)t) { + opt_printf_stderr("%s: epoch time out of range %s\n", + prog, opt_arg()); + return 0; + } + X509_VERIFY_PARAM_set_time(vpm, (time_t)t); + break; + case OPT_V_VERIFY_HOSTNAME: + if (!X509_VERIFY_PARAM_set1_host(vpm, opt_arg(), 0)) + return 0; + break; + case OPT_V_VERIFY_EMAIL: + if (!X509_VERIFY_PARAM_set1_email(vpm, opt_arg(), 0)) + return 0; + break; + case OPT_V_VERIFY_IP: + if (!X509_VERIFY_PARAM_set1_ip_asc(vpm, opt_arg())) + return 0; + break; + case OPT_V_IGNORE_CRITICAL: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_IGNORE_CRITICAL); + break; + case OPT_V_ISSUER_CHECKS: + /* NOP, deprecated */ + break; + case OPT_V_CRL_CHECK: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_CRL_CHECK); + break; + case OPT_V_CRL_CHECK_ALL: + X509_VERIFY_PARAM_set_flags(vpm, + X509_V_FLAG_CRL_CHECK | + X509_V_FLAG_CRL_CHECK_ALL); + break; + case OPT_V_POLICY_CHECK: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_POLICY_CHECK); + break; + case OPT_V_EXPLICIT_POLICY: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_EXPLICIT_POLICY); + break; + case OPT_V_INHIBIT_ANY: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_INHIBIT_ANY); + break; + case OPT_V_INHIBIT_MAP: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_INHIBIT_MAP); + break; + case OPT_V_X509_STRICT: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_X509_STRICT); + break; + case OPT_V_EXTENDED_CRL: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_EXTENDED_CRL_SUPPORT); + break; + case OPT_V_USE_DELTAS: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_USE_DELTAS); + break; + case OPT_V_POLICY_PRINT: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_NOTIFY_POLICY); + break; + case OPT_V_CHECK_SS_SIG: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_CHECK_SS_SIGNATURE); + break; + case OPT_V_TRUSTED_FIRST: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_TRUSTED_FIRST); + break; + case OPT_V_SUITEB_128_ONLY: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_SUITEB_128_LOS_ONLY); + break; + case OPT_V_SUITEB_128: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_SUITEB_128_LOS); + break; + case OPT_V_SUITEB_192: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_SUITEB_192_LOS); + break; + case OPT_V_PARTIAL_CHAIN: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_PARTIAL_CHAIN); + break; + case OPT_V_NO_ALT_CHAINS: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_NO_ALT_CHAINS); + break; + case OPT_V_NO_CHECK_TIME: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_NO_CHECK_TIME); + break; + case OPT_V_ALLOW_PROXY_CERTS: + X509_VERIFY_PARAM_set_flags(vpm, X509_V_FLAG_ALLOW_PROXY_CERTS); + break; + } + return 1; + +} + +void opt_begin(void) +{ + opt_index = 1; + arg = NULL; + flag = NULL; +} + +/* + * Parse the next flag (and value if specified), return 0 if done, -1 on + * error, otherwise the flag's retval. + */ +int opt_next(void) +{ + char *p; + const OPTIONS *o; + int ival; + long lval; + unsigned long ulval; + ossl_intmax_t imval; + ossl_uintmax_t umval; + + /* Look at current arg; at end of the list? */ + arg = NULL; + p = argv[opt_index]; + if (p == NULL) + return 0; + + /* If word doesn't start with a -, we're done. */ + if (*p != '-') + return 0; + + /* Hit "--" ? We're done. */ + opt_index++; + if (strcmp(p, "--") == 0) + return 0; + + /* Allow -nnn and --nnn */ + if (*++p == '-') + p++; + flag = p - 1; + + /* If we have --flag=foo, snip it off */ + if ((arg = strchr(p, '=')) != NULL) + *arg++ = '\0'; + for (o = opts; o->name; ++o) { + /* If not this option, move on to the next one. */ + if (!(strcmp(p, "h") == 0 && strcmp(o->name, "help") == 0) + && strcmp(p, o->name) != 0) + continue; + + /* If it doesn't take a value, make sure none was given. */ + if (o->valtype == 0 || o->valtype == '-') { + if (arg) { + opt_printf_stderr("%s: Option -%s does not take a value\n", + prog, p); + return -1; + } + return o->retval; + } + + /* Want a value; get the next param if =foo not used. */ + if (arg == NULL) { + if (argv[opt_index] == NULL) { + opt_printf_stderr("%s: Option -%s needs a value\n", + prog, o->name); + return -1; + } + arg = argv[opt_index++]; + } + + /* Syntax-check value. */ + switch (o->valtype) { + default: + case 's': + case ':': + /* Just a string. */ + break; + case '.': + /* Parameters */ + break; + case '/': + if (opt_isdir(arg) > 0) + break; + opt_printf_stderr("%s: Not a directory: %s\n", prog, arg); + return -1; + case '<': + /* Input file. */ + break; + case '>': + /* Output file. */ + break; + case 'p': + case 'n': + case 'N': + if (!opt_int(arg, &ival)) + return -1; + if (o->valtype == 'p' && ival <= 0) { + opt_printf_stderr("%s: Non-positive number \"%s\" for option -%s\n", + prog, arg, o->name); + return -1; + } + if (o->valtype == 'N' && ival < 0) { + opt_printf_stderr("%s: Negative number \"%s\" for option -%s\n", + prog, arg, o->name); + return -1; + } + break; + case 'M': + if (!opt_intmax(arg, &imval)) + return -1; + break; + case 'U': + if (!opt_uintmax(arg, &umval)) + return -1; + break; + case 'l': + if (!opt_long(arg, &lval)) + return -1; + break; + case 'u': + if (!opt_ulong(arg, &ulval)) + return -1; + break; + case 'c': + case 'E': + case 'F': + case 'f': + if (opt_format(arg, + o->valtype == 'c' ? OPT_FMT_PDS : + o->valtype == 'E' ? OPT_FMT_PDE : + o->valtype == 'F' ? OPT_FMT_PEMDER + : OPT_FMT_ANY, &ival)) + break; + opt_printf_stderr("%s: Invalid format \"%s\" for option -%s\n", + prog, arg, o->name); + return -1; + } + + /* Return the flag value. */ + return o->retval; + } + if (unknown != NULL) { + dunno = p; + return unknown->retval; + } + opt_printf_stderr("%s: Unknown option: -%s\n", prog, p); + return -1; +} + +/* Return the most recent flag parameter. */ +char *opt_arg(void) +{ + return arg; +} + +/* Return the most recent flag (option name including the preceding '-'). */ +char *opt_flag(void) +{ + return flag; +} + +/* Return the unknown option. */ +char *opt_unknown(void) +{ + return dunno; +} + +/* Return the rest of the arguments after parsing flags. */ +char **opt_rest(void) +{ + return &argv[opt_index]; +} + +/* How many items in remaining args? */ +int opt_num_rest(void) +{ + int i = 0; + char **pp; + + for (pp = opt_rest(); *pp; pp++, i++) + continue; + return i; +} + +/* Return a string describing the parameter type. */ +static const char *valtype2param(const OPTIONS *o) +{ + switch (o->valtype) { + case 0: + case '-': + return ""; + case ':': + return "uri"; + case 's': + return "val"; + case '/': + return "dir"; + case '<': + return "infile"; + case '>': + return "outfile"; + case 'p': + return "+int"; + case 'n': + return "int"; + case 'l': + return "long"; + case 'u': + return "ulong"; + case 'E': + return "PEM|DER|ENGINE"; + case 'F': + return "PEM|DER"; + case 'f': + return "format"; + case 'M': + return "intmax"; + case 'N': + return "nonneg"; + case 'U': + return "uintmax"; + } + return "parm"; +} + +static void opt_print(const OPTIONS *o, int doingparams, int width) +{ + const char* help; + char start[80 + 1]; + char *p; + + help = o->helpstr ? o->helpstr : "(No additional info)"; + if (o->name == OPT_HELP_STR) { + opt_printf_stderr(help, prog); + return; + } + if (o->name == OPT_SECTION_STR) { + opt_printf_stderr("\n"); + opt_printf_stderr(help, prog); + return; + } + if (o->name == OPT_PARAM_STR) { + opt_printf_stderr("\nParameters:\n"); + return; + } + + /* Pad out prefix */ + memset(start, ' ', sizeof(start) - 1); + start[sizeof(start) - 1] = '\0'; + + if (o->name == OPT_MORE_STR) { + /* Continuation of previous line; pad and print. */ + start[width] = '\0'; + opt_printf_stderr("%s %s\n", start, help); + return; + } + + /* Build up the "-flag [param]" part. */ + p = start; + *p++ = ' '; + if (!doingparams) + *p++ = '-'; + if (o->name[0]) + p += strlen(strcpy(p, o->name)); + else + *p++ = '*'; + if (o->valtype != '-') { + *p++ = ' '; + p += strlen(strcpy(p, valtype2param(o))); + } + *p = ' '; + if ((int)(p - start) >= MAX_OPT_HELP_WIDTH) { + *p = '\0'; + opt_printf_stderr("%s\n", start); + memset(start, ' ', sizeof(start)); + } + start[width] = '\0'; + opt_printf_stderr("%s %s\n", start, help); +} + +void opt_help(const OPTIONS *list) +{ + const OPTIONS *o; + int i, sawparams = 0, width = 5; + int standard_prolog; + char start[80 + 1]; + + /* Starts with its own help message? */ + standard_prolog = list[0].name != OPT_HELP_STR; + + /* Find the widest help. */ + for (o = list; o->name; o++) { + if (o->name == OPT_MORE_STR) + continue; + i = 2 + (int)strlen(o->name); + if (o->valtype != '-') + i += 1 + strlen(valtype2param(o)); + if (i < MAX_OPT_HELP_WIDTH && i > width) + width = i; + OPENSSL_assert(i < (int)sizeof(start)); + } + + if (standard_prolog) { + opt_printf_stderr("Usage: %s [options]\n", prog); + if (list[0].name != OPT_SECTION_STR) + opt_printf_stderr("Valid options are:\n", prog); + } + + /* Now let's print. */ + for (o = list; o->name; o++) { + if (o->name == OPT_PARAM_STR) + sawparams = 1; + opt_print(o, sawparams, width); + } +} + +/* opt_isdir section */ +#ifdef _WIN32 +# include <windows.h> +int opt_isdir(const char *name) +{ + DWORD attr; +# if defined(UNICODE) || defined(_UNICODE) + size_t i, len_0 = strlen(name) + 1; + WCHAR tempname[MAX_PATH]; + + if (len_0 > MAX_PATH) + return -1; + +# if !defined(_WIN32_WCE) || _WIN32_WCE>=101 + if (!MultiByteToWideChar(CP_ACP, 0, name, len_0, tempname, MAX_PATH)) +# endif + for (i = 0; i < len_0; i++) + tempname[i] = (WCHAR)name[i]; + + attr = GetFileAttributes(tempname); +# else + attr = GetFileAttributes(name); +# endif + if (attr == INVALID_FILE_ATTRIBUTES) + return -1; + return ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0); +} +#else +# include <sys/stat.h> +# ifndef S_ISDIR +# if defined(_S_IFMT) && defined(_S_IFDIR) +# define S_ISDIR(a) (((a) & _S_IFMT) == _S_IFDIR) +# else +# define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR) +# endif +# endif + +int opt_isdir(const char *name) +{ +# if defined(S_ISDIR) + struct stat st; + + if (stat(name, &st) == 0) + return S_ISDIR(st.st_mode); + else + return -1; +# else + return -1; +# endif +} +#endif diff --git a/apps/lib/s_cb.c b/apps/lib/s_cb.c new file mode 100644 index 000000000000..9f33c24c4e35 --- /dev/null +++ b/apps/lib/s_cb.c @@ -0,0 +1,1592 @@ +/* + * Copyright 1995-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 + */ + +/* callback functions used by s_client, s_server, and s_time */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> /* for memcpy() and strcmp() */ +#include "apps.h" +#include <openssl/core_names.h> +#include <openssl/params.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/x509.h> +#include <openssl/ssl.h> +#include <openssl/bn.h> +#ifndef OPENSSL_NO_DH +# include <openssl/dh.h> +#endif +#include "s_apps.h" + +#define COOKIE_SECRET_LENGTH 16 + +VERIFY_CB_ARGS verify_args = { -1, 0, X509_V_OK, 0 }; + +#ifndef OPENSSL_NO_SOCK +static unsigned char cookie_secret[COOKIE_SECRET_LENGTH]; +static int cookie_initialized = 0; +#endif +static BIO *bio_keylog = NULL; + +static const char *lookup(int val, const STRINT_PAIR* list, const char* def) +{ + for ( ; list->name; ++list) + if (list->retval == val) + return list->name; + return def; +} + +int verify_callback(int ok, X509_STORE_CTX *ctx) +{ + X509 *err_cert; + int err, depth; + + err_cert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + depth = X509_STORE_CTX_get_error_depth(ctx); + + if (!verify_args.quiet || !ok) { + BIO_printf(bio_err, "depth=%d ", depth); + if (err_cert != NULL) { + X509_NAME_print_ex(bio_err, + X509_get_subject_name(err_cert), + 0, get_nameopt()); + BIO_puts(bio_err, "\n"); + } else { + BIO_puts(bio_err, "<no cert>\n"); + } + } + if (!ok) { + BIO_printf(bio_err, "verify error:num=%d:%s\n", err, + X509_verify_cert_error_string(err)); + if (verify_args.depth < 0 || verify_args.depth >= depth) { + if (!verify_args.return_error) + ok = 1; + verify_args.error = err; + } else { + ok = 0; + verify_args.error = X509_V_ERR_CERT_CHAIN_TOO_LONG; + } + } + switch (err) { + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + if (err_cert != NULL) { + BIO_puts(bio_err, "issuer= "); + X509_NAME_print_ex(bio_err, X509_get_issuer_name(err_cert), + 0, get_nameopt()); + BIO_puts(bio_err, "\n"); + } + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + if (err_cert != NULL) { + BIO_printf(bio_err, "notBefore="); + ASN1_TIME_print(bio_err, X509_get0_notBefore(err_cert)); + BIO_printf(bio_err, "\n"); + } + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + if (err_cert != NULL) { + BIO_printf(bio_err, "notAfter="); + ASN1_TIME_print(bio_err, X509_get0_notAfter(err_cert)); + BIO_printf(bio_err, "\n"); + } + break; + case X509_V_ERR_NO_EXPLICIT_POLICY: + if (!verify_args.quiet) + policies_print(ctx); + break; + } + if (err == X509_V_OK && ok == 2 && !verify_args.quiet) + policies_print(ctx); + if (ok && !verify_args.quiet) + BIO_printf(bio_err, "verify return:%d\n", ok); + return ok; +} + +int set_cert_stuff(SSL_CTX *ctx, char *cert_file, char *key_file) +{ + if (cert_file != NULL) { + if (SSL_CTX_use_certificate_file(ctx, cert_file, + SSL_FILETYPE_PEM) <= 0) { + BIO_printf(bio_err, "unable to get certificate from '%s'\n", + cert_file); + ERR_print_errors(bio_err); + return 0; + } + if (key_file == NULL) + key_file = cert_file; + if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) { + BIO_printf(bio_err, "unable to get private key from '%s'\n", + key_file); + ERR_print_errors(bio_err); + return 0; + } + + /* + * If we are using DSA, we can copy the parameters from the private + * key + */ + + /* + * Now we know that a key and cert have been set against the SSL + * context + */ + if (!SSL_CTX_check_private_key(ctx)) { + BIO_printf(bio_err, + "Private key does not match the certificate public key\n"); + return 0; + } + } + return 1; +} + +int set_cert_key_stuff(SSL_CTX *ctx, X509 *cert, EVP_PKEY *key, + STACK_OF(X509) *chain, int build_chain) +{ + int chflags = chain ? SSL_BUILD_CHAIN_FLAG_CHECK : 0; + + if (cert == NULL) + return 1; + if (SSL_CTX_use_certificate(ctx, cert) <= 0) { + BIO_printf(bio_err, "error setting certificate\n"); + ERR_print_errors(bio_err); + return 0; + } + + if (SSL_CTX_use_PrivateKey(ctx, key) <= 0) { + BIO_printf(bio_err, "error setting private key\n"); + ERR_print_errors(bio_err); + return 0; + } + + /* + * Now we know that a key and cert have been set against the SSL context + */ + if (!SSL_CTX_check_private_key(ctx)) { + BIO_printf(bio_err, + "Private key does not match the certificate public key\n"); + return 0; + } + if (chain && !SSL_CTX_set1_chain(ctx, chain)) { + BIO_printf(bio_err, "error setting certificate chain\n"); + ERR_print_errors(bio_err); + return 0; + } + if (build_chain && !SSL_CTX_build_cert_chain(ctx, chflags)) { + BIO_printf(bio_err, "error building certificate chain\n"); + ERR_print_errors(bio_err); + return 0; + } + return 1; +} + +static STRINT_PAIR cert_type_list[] = { + {"RSA sign", TLS_CT_RSA_SIGN}, + {"DSA sign", TLS_CT_DSS_SIGN}, + {"RSA fixed DH", TLS_CT_RSA_FIXED_DH}, + {"DSS fixed DH", TLS_CT_DSS_FIXED_DH}, + {"ECDSA sign", TLS_CT_ECDSA_SIGN}, + {"RSA fixed ECDH", TLS_CT_RSA_FIXED_ECDH}, + {"ECDSA fixed ECDH", TLS_CT_ECDSA_FIXED_ECDH}, + {"GOST01 Sign", TLS_CT_GOST01_SIGN}, + {"GOST12 Sign", TLS_CT_GOST12_IANA_SIGN}, + {NULL} +}; + +static void ssl_print_client_cert_types(BIO *bio, SSL *s) +{ + const unsigned char *p; + int i; + int cert_type_num = SSL_get0_certificate_types(s, &p); + + if (!cert_type_num) + return; + BIO_puts(bio, "Client Certificate Types: "); + for (i = 0; i < cert_type_num; i++) { + unsigned char cert_type = p[i]; + const char *cname = lookup((int)cert_type, cert_type_list, NULL); + + if (i) + BIO_puts(bio, ", "); + if (cname != NULL) + BIO_puts(bio, cname); + else + BIO_printf(bio, "UNKNOWN (%d),", cert_type); + } + BIO_puts(bio, "\n"); +} + +static const char *get_sigtype(int nid) +{ + switch (nid) { + case EVP_PKEY_RSA: + return "RSA"; + + case EVP_PKEY_RSA_PSS: + return "RSA-PSS"; + + case EVP_PKEY_DSA: + return "DSA"; + + case EVP_PKEY_EC: + return "ECDSA"; + + case NID_ED25519: + return "ed25519"; + + case NID_ED448: + return "ed448"; + + case NID_id_GostR3410_2001: + return "gost2001"; + + case NID_id_GostR3410_2012_256: + return "gost2012_256"; + + case NID_id_GostR3410_2012_512: + return "gost2012_512"; + + default: + return NULL; + } +} + +static int do_print_sigalgs(BIO *out, SSL *s, int shared) +{ + int i, nsig, client; + + client = SSL_is_server(s) ? 0 : 1; + if (shared) + nsig = SSL_get_shared_sigalgs(s, 0, NULL, NULL, NULL, NULL, NULL); + else + nsig = SSL_get_sigalgs(s, -1, NULL, NULL, NULL, NULL, NULL); + if (nsig == 0) + return 1; + + if (shared) + BIO_puts(out, "Shared "); + + if (client) + BIO_puts(out, "Requested "); + BIO_puts(out, "Signature Algorithms: "); + for (i = 0; i < nsig; i++) { + int hash_nid, sign_nid; + unsigned char rhash, rsign; + const char *sstr = NULL; + if (shared) + SSL_get_shared_sigalgs(s, i, &sign_nid, &hash_nid, NULL, + &rsign, &rhash); + else + SSL_get_sigalgs(s, i, &sign_nid, &hash_nid, NULL, &rsign, &rhash); + if (i) + BIO_puts(out, ":"); + switch (rsign | rhash << 8) { + case 0x0809: + BIO_puts(out, "rsa_pss_pss_sha256"); + continue; + case 0x080a: + BIO_puts(out, "rsa_pss_pss_sha384"); + continue; + case 0x080b: + BIO_puts(out, "rsa_pss_pss_sha512"); + continue; + case 0x081a: + BIO_puts(out, "ecdsa_brainpoolP256r1_sha256"); + continue; + case 0x081b: + BIO_puts(out, "ecdsa_brainpoolP384r1_sha384"); + continue; + case 0x081c: + BIO_puts(out, "ecdsa_brainpoolP512r1_sha512"); + continue; + } + sstr = get_sigtype(sign_nid); + if (sstr) + BIO_printf(out, "%s", sstr); + else + BIO_printf(out, "0x%02X", (int)rsign); + if (hash_nid != NID_undef) + BIO_printf(out, "+%s", OBJ_nid2sn(hash_nid)); + else if (sstr == NULL) + BIO_printf(out, "+0x%02X", (int)rhash); + } + BIO_puts(out, "\n"); + return 1; +} + +int ssl_print_sigalgs(BIO *out, SSL *s) +{ + int nid; + + if (!SSL_is_server(s)) + ssl_print_client_cert_types(out, s); + do_print_sigalgs(out, s, 0); + do_print_sigalgs(out, s, 1); + if (SSL_get_peer_signature_nid(s, &nid) && nid != NID_undef) + BIO_printf(out, "Peer signing digest: %s\n", OBJ_nid2sn(nid)); + if (SSL_get_peer_signature_type_nid(s, &nid)) + BIO_printf(out, "Peer signature type: %s\n", get_sigtype(nid)); + return 1; +} + +#ifndef OPENSSL_NO_EC +int ssl_print_point_formats(BIO *out, SSL *s) +{ + int i, nformats; + const char *pformats; + + nformats = SSL_get0_ec_point_formats(s, &pformats); + if (nformats <= 0) + return 1; + BIO_puts(out, "Supported Elliptic Curve Point Formats: "); + for (i = 0; i < nformats; i++, pformats++) { + if (i) + BIO_puts(out, ":"); + switch (*pformats) { + case TLSEXT_ECPOINTFORMAT_uncompressed: + BIO_puts(out, "uncompressed"); + break; + + case TLSEXT_ECPOINTFORMAT_ansiX962_compressed_prime: + BIO_puts(out, "ansiX962_compressed_prime"); + break; + + case TLSEXT_ECPOINTFORMAT_ansiX962_compressed_char2: + BIO_puts(out, "ansiX962_compressed_char2"); + break; + + default: + BIO_printf(out, "unknown(%d)", (int)*pformats); + break; + + } + } + BIO_puts(out, "\n"); + return 1; +} + +int ssl_print_groups(BIO *out, SSL *s, int noshared) +{ + int i, ngroups, *groups, nid; + + ngroups = SSL_get1_groups(s, NULL); + if (ngroups <= 0) + return 1; + groups = app_malloc(ngroups * sizeof(int), "groups to print"); + SSL_get1_groups(s, groups); + + BIO_puts(out, "Supported groups: "); + for (i = 0; i < ngroups; i++) { + if (i) + BIO_puts(out, ":"); + nid = groups[i]; + BIO_printf(out, "%s", SSL_group_to_name(s, nid)); + } + OPENSSL_free(groups); + if (noshared) { + BIO_puts(out, "\n"); + return 1; + } + BIO_puts(out, "\nShared groups: "); + ngroups = SSL_get_shared_group(s, -1); + for (i = 0; i < ngroups; i++) { + if (i) + BIO_puts(out, ":"); + nid = SSL_get_shared_group(s, i); + BIO_printf(out, "%s", SSL_group_to_name(s, nid)); + } + if (ngroups == 0) + BIO_puts(out, "NONE"); + BIO_puts(out, "\n"); + return 1; +} +#endif + +int ssl_print_tmp_key(BIO *out, SSL *s) +{ + EVP_PKEY *key; + + if (!SSL_get_peer_tmp_key(s, &key)) + return 1; + BIO_puts(out, "Server Temp Key: "); + switch (EVP_PKEY_get_id(key)) { + case EVP_PKEY_RSA: + BIO_printf(out, "RSA, %d bits\n", EVP_PKEY_get_bits(key)); + break; + + case EVP_PKEY_DH: + BIO_printf(out, "DH, %d bits\n", EVP_PKEY_get_bits(key)); + break; +#ifndef OPENSSL_NO_EC + case EVP_PKEY_EC: + { + char name[80]; + size_t name_len; + + if (!EVP_PKEY_get_utf8_string_param(key, OSSL_PKEY_PARAM_GROUP_NAME, + name, sizeof(name), &name_len)) + strcpy(name, "?"); + BIO_printf(out, "ECDH, %s, %d bits\n", name, EVP_PKEY_get_bits(key)); + } + break; +#endif + default: + BIO_printf(out, "%s, %d bits\n", OBJ_nid2sn(EVP_PKEY_get_id(key)), + EVP_PKEY_get_bits(key)); + } + EVP_PKEY_free(key); + return 1; +} + +long bio_dump_callback(BIO *bio, int cmd, const char *argp, size_t len, + int argi, long argl, int ret, size_t *processed) +{ + BIO *out; + + out = (BIO *)BIO_get_callback_arg(bio); + if (out == NULL) + return ret; + + if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) { + if (ret > 0 && processed != NULL) { + BIO_printf(out, "read from %p [%p] (%zu bytes => %zu (0x%zX))\n", + (void *)bio, (void *)argp, len, *processed, *processed); + BIO_dump(out, argp, (int)*processed); + } else { + BIO_printf(out, "read from %p [%p] (%zu bytes => %d)\n", + (void *)bio, (void *)argp, len, ret); + } + } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) { + if (ret > 0 && processed != NULL) { + BIO_printf(out, "write to %p [%p] (%zu bytes => %zu (0x%zX))\n", + (void *)bio, (void *)argp, len, *processed, *processed); + BIO_dump(out, argp, (int)*processed); + } else { + BIO_printf(out, "write to %p [%p] (%zu bytes => %d)\n", + (void *)bio, (void *)argp, len, ret); + } + } + return ret; +} + +void apps_ssl_info_callback(const SSL *s, int where, int ret) +{ + const char *str; + int w; + + w = where & ~SSL_ST_MASK; + + if (w & SSL_ST_CONNECT) + str = "SSL_connect"; + else if (w & SSL_ST_ACCEPT) + str = "SSL_accept"; + else + str = "undefined"; + + if (where & SSL_CB_LOOP) { + BIO_printf(bio_err, "%s:%s\n", str, SSL_state_string_long(s)); + } else if (where & SSL_CB_ALERT) { + str = (where & SSL_CB_READ) ? "read" : "write"; + BIO_printf(bio_err, "SSL3 alert %s:%s:%s\n", + str, + SSL_alert_type_string_long(ret), + SSL_alert_desc_string_long(ret)); + } else if (where & SSL_CB_EXIT) { + if (ret == 0) + BIO_printf(bio_err, "%s:failed in %s\n", + str, SSL_state_string_long(s)); + else if (ret < 0) + BIO_printf(bio_err, "%s:error in %s\n", + str, SSL_state_string_long(s)); + } +} + +static STRINT_PAIR ssl_versions[] = { + {"SSL 3.0", SSL3_VERSION}, + {"TLS 1.0", TLS1_VERSION}, + {"TLS 1.1", TLS1_1_VERSION}, + {"TLS 1.2", TLS1_2_VERSION}, + {"TLS 1.3", TLS1_3_VERSION}, + {"DTLS 1.0", DTLS1_VERSION}, + {"DTLS 1.0 (bad)", DTLS1_BAD_VER}, + {NULL} +}; + +static STRINT_PAIR alert_types[] = { + {" close_notify", 0}, + {" end_of_early_data", 1}, + {" unexpected_message", 10}, + {" bad_record_mac", 20}, + {" decryption_failed", 21}, + {" record_overflow", 22}, + {" decompression_failure", 30}, + {" handshake_failure", 40}, + {" bad_certificate", 42}, + {" unsupported_certificate", 43}, + {" certificate_revoked", 44}, + {" certificate_expired", 45}, + {" certificate_unknown", 46}, + {" illegal_parameter", 47}, + {" unknown_ca", 48}, + {" access_denied", 49}, + {" decode_error", 50}, + {" decrypt_error", 51}, + {" export_restriction", 60}, + {" protocol_version", 70}, + {" insufficient_security", 71}, + {" internal_error", 80}, + {" inappropriate_fallback", 86}, + {" user_canceled", 90}, + {" no_renegotiation", 100}, + {" missing_extension", 109}, + {" unsupported_extension", 110}, + {" certificate_unobtainable", 111}, + {" unrecognized_name", 112}, + {" bad_certificate_status_response", 113}, + {" bad_certificate_hash_value", 114}, + {" unknown_psk_identity", 115}, + {" certificate_required", 116}, + {NULL} +}; + +static STRINT_PAIR handshakes[] = { + {", HelloRequest", SSL3_MT_HELLO_REQUEST}, + {", ClientHello", SSL3_MT_CLIENT_HELLO}, + {", ServerHello", SSL3_MT_SERVER_HELLO}, + {", HelloVerifyRequest", DTLS1_MT_HELLO_VERIFY_REQUEST}, + {", NewSessionTicket", SSL3_MT_NEWSESSION_TICKET}, + {", EndOfEarlyData", SSL3_MT_END_OF_EARLY_DATA}, + {", EncryptedExtensions", SSL3_MT_ENCRYPTED_EXTENSIONS}, + {", Certificate", SSL3_MT_CERTIFICATE}, + {", ServerKeyExchange", SSL3_MT_SERVER_KEY_EXCHANGE}, + {", CertificateRequest", SSL3_MT_CERTIFICATE_REQUEST}, + {", ServerHelloDone", SSL3_MT_SERVER_DONE}, + {", CertificateVerify", SSL3_MT_CERTIFICATE_VERIFY}, + {", ClientKeyExchange", SSL3_MT_CLIENT_KEY_EXCHANGE}, + {", Finished", SSL3_MT_FINISHED}, + {", CertificateUrl", SSL3_MT_CERTIFICATE_URL}, + {", CertificateStatus", SSL3_MT_CERTIFICATE_STATUS}, + {", SupplementalData", SSL3_MT_SUPPLEMENTAL_DATA}, + {", KeyUpdate", SSL3_MT_KEY_UPDATE}, +#ifndef OPENSSL_NO_NEXTPROTONEG + {", NextProto", SSL3_MT_NEXT_PROTO}, +#endif + {", MessageHash", SSL3_MT_MESSAGE_HASH}, + {NULL} +}; + +void msg_cb(int write_p, int version, int content_type, const void *buf, + size_t len, SSL *ssl, void *arg) +{ + BIO *bio = arg; + const char *str_write_p = write_p ? ">>>" : "<<<"; + char tmpbuf[128]; + const char *str_version, *str_content_type = "", *str_details1 = "", *str_details2 = ""; + const unsigned char* bp = buf; + + if (version == SSL3_VERSION || + version == TLS1_VERSION || + version == TLS1_1_VERSION || + version == TLS1_2_VERSION || + version == TLS1_3_VERSION || + version == DTLS1_VERSION || version == DTLS1_BAD_VER) { + str_version = lookup(version, ssl_versions, "???"); + switch (content_type) { + case SSL3_RT_CHANGE_CIPHER_SPEC: + /* type 20 */ + str_content_type = ", ChangeCipherSpec"; + break; + case SSL3_RT_ALERT: + /* type 21 */ + str_content_type = ", Alert"; + str_details1 = ", ???"; + if (len == 2) { + switch (bp[0]) { + case 1: + str_details1 = ", warning"; + break; + case 2: + str_details1 = ", fatal"; + break; + } + str_details2 = lookup((int)bp[1], alert_types, " ???"); + } + break; + case SSL3_RT_HANDSHAKE: + /* type 22 */ + str_content_type = ", Handshake"; + str_details1 = "???"; + if (len > 0) + str_details1 = lookup((int)bp[0], handshakes, "???"); + break; + case SSL3_RT_APPLICATION_DATA: + /* type 23 */ + str_content_type = ", ApplicationData"; + break; + case SSL3_RT_HEADER: + /* type 256 */ + str_content_type = ", RecordHeader"; + break; + case SSL3_RT_INNER_CONTENT_TYPE: + /* type 257 */ + str_content_type = ", InnerContent"; + break; + default: + BIO_snprintf(tmpbuf, sizeof(tmpbuf)-1, ", Unknown (content_type=%d)", content_type); + str_content_type = tmpbuf; + } + } else { + BIO_snprintf(tmpbuf, sizeof(tmpbuf)-1, "Not TLS data or unknown version (version=%d, content_type=%d)", version, content_type); + str_version = tmpbuf; + } + + BIO_printf(bio, "%s %s%s [length %04lx]%s%s\n", str_write_p, str_version, + str_content_type, (unsigned long)len, str_details1, + str_details2); + + if (len > 0) { + size_t num, i; + + BIO_printf(bio, " "); + num = len; + for (i = 0; i < num; i++) { + if (i % 16 == 0 && i > 0) + BIO_printf(bio, "\n "); + BIO_printf(bio, " %02x", ((const unsigned char *)buf)[i]); + } + if (i < len) + BIO_printf(bio, " ..."); + BIO_printf(bio, "\n"); + } + (void)BIO_flush(bio); +} + +static const STRINT_PAIR tlsext_types[] = { + {"server name", TLSEXT_TYPE_server_name}, + {"max fragment length", TLSEXT_TYPE_max_fragment_length}, + {"client certificate URL", TLSEXT_TYPE_client_certificate_url}, + {"trusted CA keys", TLSEXT_TYPE_trusted_ca_keys}, + {"truncated HMAC", TLSEXT_TYPE_truncated_hmac}, + {"status request", TLSEXT_TYPE_status_request}, + {"user mapping", TLSEXT_TYPE_user_mapping}, + {"client authz", TLSEXT_TYPE_client_authz}, + {"server authz", TLSEXT_TYPE_server_authz}, + {"cert type", TLSEXT_TYPE_cert_type}, + {"supported_groups", TLSEXT_TYPE_supported_groups}, + {"EC point formats", TLSEXT_TYPE_ec_point_formats}, + {"SRP", TLSEXT_TYPE_srp}, + {"signature algorithms", TLSEXT_TYPE_signature_algorithms}, + {"use SRTP", TLSEXT_TYPE_use_srtp}, + {"session ticket", TLSEXT_TYPE_session_ticket}, + {"renegotiation info", TLSEXT_TYPE_renegotiate}, + {"signed certificate timestamps", TLSEXT_TYPE_signed_certificate_timestamp}, + {"TLS padding", TLSEXT_TYPE_padding}, +#ifdef TLSEXT_TYPE_next_proto_neg + {"next protocol", TLSEXT_TYPE_next_proto_neg}, +#endif +#ifdef TLSEXT_TYPE_encrypt_then_mac + {"encrypt-then-mac", TLSEXT_TYPE_encrypt_then_mac}, +#endif +#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation + {"application layer protocol negotiation", + TLSEXT_TYPE_application_layer_protocol_negotiation}, +#endif +#ifdef TLSEXT_TYPE_extended_master_secret + {"extended master secret", TLSEXT_TYPE_extended_master_secret}, +#endif + {"key share", TLSEXT_TYPE_key_share}, + {"supported versions", TLSEXT_TYPE_supported_versions}, + {"psk", TLSEXT_TYPE_psk}, + {"psk kex modes", TLSEXT_TYPE_psk_kex_modes}, + {"certificate authorities", TLSEXT_TYPE_certificate_authorities}, + {"post handshake auth", TLSEXT_TYPE_post_handshake_auth}, + {"early_data", TLSEXT_TYPE_early_data}, + {NULL} +}; + +/* from rfc8446 4.2.3. + gost (https://tools.ietf.org/id/draft-smyshlyaev-tls12-gost-suites-04.html) */ +static STRINT_PAIR signature_tls13_scheme_list[] = { + {"rsa_pkcs1_sha1", 0x0201 /* TLSEXT_SIGALG_rsa_pkcs1_sha1 */}, + {"ecdsa_sha1", 0x0203 /* TLSEXT_SIGALG_ecdsa_sha1 */}, +/* {"rsa_pkcs1_sha224", 0x0301 TLSEXT_SIGALG_rsa_pkcs1_sha224}, not in rfc8446 */ +/* {"ecdsa_sha224", 0x0303 TLSEXT_SIGALG_ecdsa_sha224} not in rfc8446 */ + {"rsa_pkcs1_sha256", 0x0401 /* TLSEXT_SIGALG_rsa_pkcs1_sha256 */}, + {"ecdsa_secp256r1_sha256", 0x0403 /* TLSEXT_SIGALG_ecdsa_secp256r1_sha256 */}, + {"rsa_pkcs1_sha384", 0x0501 /* TLSEXT_SIGALG_rsa_pkcs1_sha384 */}, + {"ecdsa_secp384r1_sha384", 0x0503 /* TLSEXT_SIGALG_ecdsa_secp384r1_sha384 */}, + {"rsa_pkcs1_sha512", 0x0601 /* TLSEXT_SIGALG_rsa_pkcs1_sha512 */}, + {"ecdsa_secp521r1_sha512", 0x0603 /* TLSEXT_SIGALG_ecdsa_secp521r1_sha512 */}, + {"rsa_pss_rsae_sha256", 0x0804 /* TLSEXT_SIGALG_rsa_pss_rsae_sha256 */}, + {"rsa_pss_rsae_sha384", 0x0805 /* TLSEXT_SIGALG_rsa_pss_rsae_sha384 */}, + {"rsa_pss_rsae_sha512", 0x0806 /* TLSEXT_SIGALG_rsa_pss_rsae_sha512 */}, + {"ed25519", 0x0807 /* TLSEXT_SIGALG_ed25519 */}, + {"ed448", 0x0808 /* TLSEXT_SIGALG_ed448 */}, + {"rsa_pss_pss_sha256", 0x0809 /* TLSEXT_SIGALG_rsa_pss_pss_sha256 */}, + {"rsa_pss_pss_sha384", 0x080a /* TLSEXT_SIGALG_rsa_pss_pss_sha384 */}, + {"rsa_pss_pss_sha512", 0x080b /* TLSEXT_SIGALG_rsa_pss_pss_sha512 */}, + {"gostr34102001", 0xeded /* TLSEXT_SIGALG_gostr34102001_gostr3411 */}, + {"gostr34102012_256", 0xeeee /* TLSEXT_SIGALG_gostr34102012_256_gostr34112012_256 */}, + {"gostr34102012_512", 0xefef /* TLSEXT_SIGALG_gostr34102012_512_gostr34112012_512 */}, + {NULL} +}; + +/* from rfc5246 7.4.1.4.1. */ +static STRINT_PAIR signature_tls12_alg_list[] = { + {"anonymous", TLSEXT_signature_anonymous /* 0 */}, + {"RSA", TLSEXT_signature_rsa /* 1 */}, + {"DSA", TLSEXT_signature_dsa /* 2 */}, + {"ECDSA", TLSEXT_signature_ecdsa /* 3 */}, + {NULL} +}; + +/* from rfc5246 7.4.1.4.1. */ +static STRINT_PAIR signature_tls12_hash_list[] = { + {"none", TLSEXT_hash_none /* 0 */}, + {"MD5", TLSEXT_hash_md5 /* 1 */}, + {"SHA1", TLSEXT_hash_sha1 /* 2 */}, + {"SHA224", TLSEXT_hash_sha224 /* 3 */}, + {"SHA256", TLSEXT_hash_sha256 /* 4 */}, + {"SHA384", TLSEXT_hash_sha384 /* 5 */}, + {"SHA512", TLSEXT_hash_sha512 /* 6 */}, + {NULL} +}; + +void tlsext_cb(SSL *s, int client_server, int type, + const unsigned char *data, int len, void *arg) +{ + BIO *bio = arg; + const char *extname = lookup(type, tlsext_types, "unknown"); + + BIO_printf(bio, "TLS %s extension \"%s\" (id=%d), len=%d\n", + client_server ? "server" : "client", extname, type, len); + BIO_dump(bio, (const char *)data, len); + (void)BIO_flush(bio); +} + +#ifndef OPENSSL_NO_SOCK +int generate_stateless_cookie_callback(SSL *ssl, unsigned char *cookie, + size_t *cookie_len) +{ + unsigned char *buffer = NULL; + size_t length = 0; + unsigned short port; + BIO_ADDR *lpeer = NULL, *peer = NULL; + int res = 0; + + /* Initialize a random secret */ + if (!cookie_initialized) { + if (RAND_bytes(cookie_secret, COOKIE_SECRET_LENGTH) <= 0) { + BIO_printf(bio_err, "error setting random cookie secret\n"); + return 0; + } + cookie_initialized = 1; + } + + if (SSL_is_dtls(ssl)) { + lpeer = peer = BIO_ADDR_new(); + if (peer == NULL) { + BIO_printf(bio_err, "memory full\n"); + return 0; + } + + /* Read peer information */ + (void)BIO_dgram_get_peer(SSL_get_rbio(ssl), peer); + } else { + peer = ourpeer; + } + + /* Create buffer with peer's address and port */ + if (!BIO_ADDR_rawaddress(peer, NULL, &length)) { + BIO_printf(bio_err, "Failed getting peer address\n"); + BIO_ADDR_free(lpeer); + return 0; + } + OPENSSL_assert(length != 0); + port = BIO_ADDR_rawport(peer); + length += sizeof(port); + buffer = app_malloc(length, "cookie generate buffer"); + + memcpy(buffer, &port, sizeof(port)); + BIO_ADDR_rawaddress(peer, buffer + sizeof(port), NULL); + + if (EVP_Q_mac(NULL, "HMAC", NULL, "SHA1", NULL, + cookie_secret, COOKIE_SECRET_LENGTH, buffer, length, + cookie, DTLS1_COOKIE_LENGTH, cookie_len) == NULL) { + BIO_printf(bio_err, + "Error calculating HMAC-SHA1 of buffer with secret\n"); + goto end; + } + res = 1; +end: + OPENSSL_free(buffer); + BIO_ADDR_free(lpeer); + + return res; +} + +int verify_stateless_cookie_callback(SSL *ssl, const unsigned char *cookie, + size_t cookie_len) +{ + unsigned char result[EVP_MAX_MD_SIZE]; + size_t resultlength; + + /* Note: we check cookie_initialized because if it's not, + * it cannot be valid */ + if (cookie_initialized + && generate_stateless_cookie_callback(ssl, result, &resultlength) + && cookie_len == resultlength + && memcmp(result, cookie, resultlength) == 0) + return 1; + + return 0; +} + +int generate_cookie_callback(SSL *ssl, unsigned char *cookie, + unsigned int *cookie_len) +{ + size_t temp = 0; + int res = generate_stateless_cookie_callback(ssl, cookie, &temp); + + if (res != 0) + *cookie_len = (unsigned int)temp; + return res; +} + +int verify_cookie_callback(SSL *ssl, const unsigned char *cookie, + unsigned int cookie_len) +{ + return verify_stateless_cookie_callback(ssl, cookie, cookie_len); +} + +#endif + +/* + * Example of extended certificate handling. Where the standard support of + * one certificate per algorithm is not sufficient an application can decide + * which certificate(s) to use at runtime based on whatever criteria it deems + * appropriate. + */ + +/* Linked list of certificates, keys and chains */ +struct ssl_excert_st { + int certform; + const char *certfile; + int keyform; + const char *keyfile; + const char *chainfile; + X509 *cert; + EVP_PKEY *key; + STACK_OF(X509) *chain; + int build_chain; + struct ssl_excert_st *next, *prev; +}; + +static STRINT_PAIR chain_flags[] = { + {"Overall Validity", CERT_PKEY_VALID}, + {"Sign with EE key", CERT_PKEY_SIGN}, + {"EE signature", CERT_PKEY_EE_SIGNATURE}, + {"CA signature", CERT_PKEY_CA_SIGNATURE}, + {"EE key parameters", CERT_PKEY_EE_PARAM}, + {"CA key parameters", CERT_PKEY_CA_PARAM}, + {"Explicitly sign with EE key", CERT_PKEY_EXPLICIT_SIGN}, + {"Issuer Name", CERT_PKEY_ISSUER_NAME}, + {"Certificate Type", CERT_PKEY_CERT_TYPE}, + {NULL} +}; + +static void print_chain_flags(SSL *s, int flags) +{ + STRINT_PAIR *pp; + + for (pp = chain_flags; pp->name; ++pp) + BIO_printf(bio_err, "\t%s: %s\n", + pp->name, + (flags & pp->retval) ? "OK" : "NOT OK"); + BIO_printf(bio_err, "\tSuite B: "); + if (SSL_set_cert_flags(s, 0) & SSL_CERT_FLAG_SUITEB_128_LOS) + BIO_puts(bio_err, flags & CERT_PKEY_SUITEB ? "OK\n" : "NOT OK\n"); + else + BIO_printf(bio_err, "not tested\n"); +} + +/* + * Very basic selection callback: just use any certificate chain reported as + * valid. More sophisticated could prioritise according to local policy. + */ +static int set_cert_cb(SSL *ssl, void *arg) +{ + int i, rv; + SSL_EXCERT *exc = arg; +#ifdef CERT_CB_TEST_RETRY + static int retry_cnt; + + if (retry_cnt < 5) { + retry_cnt++; + BIO_printf(bio_err, + "Certificate callback retry test: count %d\n", + retry_cnt); + return -1; + } +#endif + SSL_certs_clear(ssl); + + if (exc == NULL) + return 1; + + /* + * Go to end of list and traverse backwards since we prepend newer + * entries this retains the original order. + */ + while (exc->next != NULL) + exc = exc->next; + + i = 0; + + while (exc != NULL) { + i++; + rv = SSL_check_chain(ssl, exc->cert, exc->key, exc->chain); + BIO_printf(bio_err, "Checking cert chain %d:\nSubject: ", i); + X509_NAME_print_ex(bio_err, X509_get_subject_name(exc->cert), 0, + get_nameopt()); + BIO_puts(bio_err, "\n"); + print_chain_flags(ssl, rv); + if (rv & CERT_PKEY_VALID) { + if (!SSL_use_certificate(ssl, exc->cert) + || !SSL_use_PrivateKey(ssl, exc->key)) { + return 0; + } + /* + * NB: we wouldn't normally do this as it is not efficient + * building chains on each connection better to cache the chain + * in advance. + */ + if (exc->build_chain) { + if (!SSL_build_cert_chain(ssl, 0)) + return 0; + } else if (exc->chain != NULL) { + if (!SSL_set1_chain(ssl, exc->chain)) + return 0; + } + } + exc = exc->prev; + } + return 1; +} + +void ssl_ctx_set_excert(SSL_CTX *ctx, SSL_EXCERT *exc) +{ + SSL_CTX_set_cert_cb(ctx, set_cert_cb, exc); +} + +static int ssl_excert_prepend(SSL_EXCERT **pexc) +{ + SSL_EXCERT *exc = app_malloc(sizeof(*exc), "prepend cert"); + + memset(exc, 0, sizeof(*exc)); + + exc->next = *pexc; + *pexc = exc; + + if (exc->next) { + exc->certform = exc->next->certform; + exc->keyform = exc->next->keyform; + exc->next->prev = exc; + } else { + exc->certform = FORMAT_PEM; + exc->keyform = FORMAT_PEM; + } + return 1; + +} + +void ssl_excert_free(SSL_EXCERT *exc) +{ + SSL_EXCERT *curr; + + if (exc == NULL) + return; + while (exc) { + X509_free(exc->cert); + EVP_PKEY_free(exc->key); + sk_X509_pop_free(exc->chain, X509_free); + curr = exc; + exc = exc->next; + OPENSSL_free(curr); + } +} + +int load_excert(SSL_EXCERT **pexc) +{ + SSL_EXCERT *exc = *pexc; + + if (exc == NULL) + return 1; + /* If nothing in list, free and set to NULL */ + if (exc->certfile == NULL && exc->next == NULL) { + ssl_excert_free(exc); + *pexc = NULL; + return 1; + } + for (; exc; exc = exc->next) { + if (exc->certfile == NULL) { + BIO_printf(bio_err, "Missing filename\n"); + return 0; + } + exc->cert = load_cert(exc->certfile, exc->certform, + "Server Certificate"); + if (exc->cert == NULL) + return 0; + if (exc->keyfile != NULL) { + exc->key = load_key(exc->keyfile, exc->keyform, + 0, NULL, NULL, "server key"); + } else { + exc->key = load_key(exc->certfile, exc->certform, + 0, NULL, NULL, "server key"); + } + if (exc->key == NULL) + return 0; + if (exc->chainfile != NULL) { + if (!load_certs(exc->chainfile, 0, &exc->chain, NULL, "server chain")) + return 0; + } + } + return 1; +} + +enum range { OPT_X_ENUM }; + +int args_excert(int opt, SSL_EXCERT **pexc) +{ + SSL_EXCERT *exc = *pexc; + + assert(opt > OPT_X__FIRST); + assert(opt < OPT_X__LAST); + + if (exc == NULL) { + if (!ssl_excert_prepend(&exc)) { + BIO_printf(bio_err, " %s: Error initialising xcert\n", + opt_getprog()); + goto err; + } + *pexc = exc; + } + + switch ((enum range)opt) { + case OPT_X__FIRST: + case OPT_X__LAST: + return 0; + case OPT_X_CERT: + if (exc->certfile != NULL && !ssl_excert_prepend(&exc)) { + BIO_printf(bio_err, "%s: Error adding xcert\n", opt_getprog()); + goto err; + } + *pexc = exc; + exc->certfile = opt_arg(); + break; + case OPT_X_KEY: + if (exc->keyfile != NULL) { + BIO_printf(bio_err, "%s: Key already specified\n", opt_getprog()); + goto err; + } + exc->keyfile = opt_arg(); + break; + case OPT_X_CHAIN: + if (exc->chainfile != NULL) { + BIO_printf(bio_err, "%s: Chain already specified\n", + opt_getprog()); + goto err; + } + exc->chainfile = opt_arg(); + break; + case OPT_X_CHAIN_BUILD: + exc->build_chain = 1; + break; + case OPT_X_CERTFORM: + if (!opt_format(opt_arg(), OPT_FMT_ANY, &exc->certform)) + return 0; + break; + case OPT_X_KEYFORM: + if (!opt_format(opt_arg(), OPT_FMT_ANY, &exc->keyform)) + return 0; + break; + } + return 1; + + err: + ERR_print_errors(bio_err); + ssl_excert_free(exc); + *pexc = NULL; + return 0; +} + +static void print_raw_cipherlist(SSL *s) +{ + const unsigned char *rlist; + static const unsigned char scsv_id[] = { 0, 0xFF }; + size_t i, rlistlen, num; + + if (!SSL_is_server(s)) + return; + num = SSL_get0_raw_cipherlist(s, NULL); + OPENSSL_assert(num == 2); + rlistlen = SSL_get0_raw_cipherlist(s, &rlist); + BIO_puts(bio_err, "Client cipher list: "); + for (i = 0; i < rlistlen; i += num, rlist += num) { + const SSL_CIPHER *c = SSL_CIPHER_find(s, rlist); + if (i) + BIO_puts(bio_err, ":"); + if (c != NULL) { + BIO_puts(bio_err, SSL_CIPHER_get_name(c)); + } else if (memcmp(rlist, scsv_id, num) == 0) { + BIO_puts(bio_err, "SCSV"); + } else { + size_t j; + BIO_puts(bio_err, "0x"); + for (j = 0; j < num; j++) + BIO_printf(bio_err, "%02X", rlist[j]); + } + } + BIO_puts(bio_err, "\n"); +} + +/* + * Hex encoder for TLSA RRdata, not ':' delimited. + */ +static char *hexencode(const unsigned char *data, size_t len) +{ + static const char *hex = "0123456789abcdef"; + char *out; + char *cp; + size_t outlen = 2 * len + 1; + int ilen = (int) outlen; + + if (outlen < len || ilen < 0 || outlen != (size_t)ilen) { + BIO_printf(bio_err, "%s: %zu-byte buffer too large to hexencode\n", + opt_getprog(), len); + exit(1); + } + cp = out = app_malloc(ilen, "TLSA hex data buffer"); + + while (len-- > 0) { + *cp++ = hex[(*data >> 4) & 0x0f]; + *cp++ = hex[*data++ & 0x0f]; + } + *cp = '\0'; + return out; +} + +void print_verify_detail(SSL *s, BIO *bio) +{ + int mdpth; + EVP_PKEY *mspki; + long verify_err = SSL_get_verify_result(s); + + if (verify_err == X509_V_OK) { + const char *peername = SSL_get0_peername(s); + + BIO_printf(bio, "Verification: OK\n"); + if (peername != NULL) + BIO_printf(bio, "Verified peername: %s\n", peername); + } else { + const char *reason = X509_verify_cert_error_string(verify_err); + + BIO_printf(bio, "Verification error: %s\n", reason); + } + + if ((mdpth = SSL_get0_dane_authority(s, NULL, &mspki)) >= 0) { + uint8_t usage, selector, mtype; + const unsigned char *data = NULL; + size_t dlen = 0; + char *hexdata; + + mdpth = SSL_get0_dane_tlsa(s, &usage, &selector, &mtype, &data, &dlen); + + /* + * The TLSA data field can be quite long when it is a certificate, + * public key or even a SHA2-512 digest. Because the initial octets of + * ASN.1 certificates and public keys contain mostly boilerplate OIDs + * and lengths, we show the last 12 bytes of the data instead, as these + * are more likely to distinguish distinct TLSA records. + */ +#define TLSA_TAIL_SIZE 12 + if (dlen > TLSA_TAIL_SIZE) + hexdata = hexencode(data + dlen - TLSA_TAIL_SIZE, TLSA_TAIL_SIZE); + else + hexdata = hexencode(data, dlen); + BIO_printf(bio, "DANE TLSA %d %d %d %s%s %s at depth %d\n", + usage, selector, mtype, + (dlen > TLSA_TAIL_SIZE) ? "..." : "", hexdata, + (mspki != NULL) ? "signed the certificate" : + mdpth ? "matched TA certificate" : "matched EE certificate", + mdpth); + OPENSSL_free(hexdata); + } +} + +void print_ssl_summary(SSL *s) +{ + const SSL_CIPHER *c; + X509 *peer; + + BIO_printf(bio_err, "Protocol version: %s\n", SSL_get_version(s)); + print_raw_cipherlist(s); + c = SSL_get_current_cipher(s); + BIO_printf(bio_err, "Ciphersuite: %s\n", SSL_CIPHER_get_name(c)); + do_print_sigalgs(bio_err, s, 0); + peer = SSL_get0_peer_certificate(s); + if (peer != NULL) { + int nid; + + BIO_puts(bio_err, "Peer certificate: "); + X509_NAME_print_ex(bio_err, X509_get_subject_name(peer), + 0, get_nameopt()); + BIO_puts(bio_err, "\n"); + if (SSL_get_peer_signature_nid(s, &nid)) + BIO_printf(bio_err, "Hash used: %s\n", OBJ_nid2sn(nid)); + if (SSL_get_peer_signature_type_nid(s, &nid)) + BIO_printf(bio_err, "Signature type: %s\n", get_sigtype(nid)); + print_verify_detail(s, bio_err); + } else { + BIO_puts(bio_err, "No peer certificate\n"); + } +#ifndef OPENSSL_NO_EC + ssl_print_point_formats(bio_err, s); + if (SSL_is_server(s)) + ssl_print_groups(bio_err, s, 1); + else + ssl_print_tmp_key(bio_err, s); +#else + if (!SSL_is_server(s)) + ssl_print_tmp_key(bio_err, s); +#endif +} + +int config_ctx(SSL_CONF_CTX *cctx, STACK_OF(OPENSSL_STRING) *str, + SSL_CTX *ctx) +{ + int i; + + SSL_CONF_CTX_set_ssl_ctx(cctx, ctx); + for (i = 0; i < sk_OPENSSL_STRING_num(str); i += 2) { + const char *flag = sk_OPENSSL_STRING_value(str, i); + const char *arg = sk_OPENSSL_STRING_value(str, i + 1); + + if (SSL_CONF_cmd(cctx, flag, arg) <= 0) { + BIO_printf(bio_err, "Call to SSL_CONF_cmd(%s, %s) failed\n", + flag, arg == NULL ? "<NULL>" : arg); + ERR_print_errors(bio_err); + return 0; + } + } + if (!SSL_CONF_CTX_finish(cctx)) { + BIO_puts(bio_err, "Error finishing context\n"); + ERR_print_errors(bio_err); + return 0; + } + return 1; +} + +static int add_crls_store(X509_STORE *st, STACK_OF(X509_CRL) *crls) +{ + X509_CRL *crl; + int i, ret = 1; + + for (i = 0; i < sk_X509_CRL_num(crls); i++) { + crl = sk_X509_CRL_value(crls, i); + if (!X509_STORE_add_crl(st, crl)) + ret = 0; + } + return ret; +} + +int ssl_ctx_add_crls(SSL_CTX *ctx, STACK_OF(X509_CRL) *crls, int crl_download) +{ + X509_STORE *st; + + st = SSL_CTX_get_cert_store(ctx); + add_crls_store(st, crls); + if (crl_download) + store_setup_crl_download(st); + return 1; +} + +int ssl_load_stores(SSL_CTX *ctx, + const char *vfyCApath, const char *vfyCAfile, + const char *vfyCAstore, + const char *chCApath, const char *chCAfile, + const char *chCAstore, + STACK_OF(X509_CRL) *crls, int crl_download) +{ + X509_STORE *vfy = NULL, *ch = NULL; + int rv = 0; + + if (vfyCApath != NULL || vfyCAfile != NULL || vfyCAstore != NULL) { + vfy = X509_STORE_new(); + if (vfy == NULL) + goto err; + if (vfyCAfile != NULL && !X509_STORE_load_file(vfy, vfyCAfile)) + goto err; + if (vfyCApath != NULL && !X509_STORE_load_path(vfy, vfyCApath)) + goto err; + if (vfyCAstore != NULL && !X509_STORE_load_store(vfy, vfyCAstore)) + goto err; + add_crls_store(vfy, crls); + if (SSL_CTX_set1_verify_cert_store(ctx, vfy) == 0) + goto err; + if (crl_download) + store_setup_crl_download(vfy); + } + if (chCApath != NULL || chCAfile != NULL || chCAstore != NULL) { + ch = X509_STORE_new(); + if (ch == NULL) + goto err; + if (chCAfile != NULL && !X509_STORE_load_file(ch, chCAfile)) + goto err; + if (chCApath != NULL && !X509_STORE_load_path(ch, chCApath)) + goto err; + if (chCAstore != NULL && !X509_STORE_load_store(ch, chCAstore)) + goto err; + if (SSL_CTX_set1_chain_cert_store(ctx, ch) == 0) + goto err; + } + rv = 1; + err: + X509_STORE_free(vfy); + X509_STORE_free(ch); + return rv; +} + +/* Verbose print out of security callback */ + +typedef struct { + BIO *out; + int verbose; + int (*old_cb) (const SSL *s, const SSL_CTX *ctx, int op, int bits, int nid, + void *other, void *ex); +} security_debug_ex; + +static STRINT_PAIR callback_types[] = { + {"Supported Ciphersuite", SSL_SECOP_CIPHER_SUPPORTED}, + {"Shared Ciphersuite", SSL_SECOP_CIPHER_SHARED}, + {"Check Ciphersuite", SSL_SECOP_CIPHER_CHECK}, +#ifndef OPENSSL_NO_DH + {"Temp DH key bits", SSL_SECOP_TMP_DH}, +#endif + {"Supported Curve", SSL_SECOP_CURVE_SUPPORTED}, + {"Shared Curve", SSL_SECOP_CURVE_SHARED}, + {"Check Curve", SSL_SECOP_CURVE_CHECK}, + {"Supported Signature Algorithm", SSL_SECOP_SIGALG_SUPPORTED}, + {"Shared Signature Algorithm", SSL_SECOP_SIGALG_SHARED}, + {"Check Signature Algorithm", SSL_SECOP_SIGALG_CHECK}, + {"Signature Algorithm mask", SSL_SECOP_SIGALG_MASK}, + {"Certificate chain EE key", SSL_SECOP_EE_KEY}, + {"Certificate chain CA key", SSL_SECOP_CA_KEY}, + {"Peer Chain EE key", SSL_SECOP_PEER_EE_KEY}, + {"Peer Chain CA key", SSL_SECOP_PEER_CA_KEY}, + {"Certificate chain CA digest", SSL_SECOP_CA_MD}, + {"Peer chain CA digest", SSL_SECOP_PEER_CA_MD}, + {"SSL compression", SSL_SECOP_COMPRESSION}, + {"Session ticket", SSL_SECOP_TICKET}, + {NULL} +}; + +static int security_callback_debug(const SSL *s, const SSL_CTX *ctx, + int op, int bits, int nid, + void *other, void *ex) +{ + security_debug_ex *sdb = ex; + int rv, show_bits = 1, cert_md = 0; + const char *nm; + int show_nm; + + rv = sdb->old_cb(s, ctx, op, bits, nid, other, ex); + if (rv == 1 && sdb->verbose < 2) + return 1; + BIO_puts(sdb->out, "Security callback: "); + + nm = lookup(op, callback_types, NULL); + show_nm = nm != NULL; + switch (op) { + case SSL_SECOP_TICKET: + case SSL_SECOP_COMPRESSION: + show_bits = 0; + show_nm = 0; + break; + case SSL_SECOP_VERSION: + BIO_printf(sdb->out, "Version=%s", lookup(nid, ssl_versions, "???")); + show_bits = 0; + show_nm = 0; + break; + case SSL_SECOP_CA_MD: + case SSL_SECOP_PEER_CA_MD: + cert_md = 1; + break; + case SSL_SECOP_SIGALG_SUPPORTED: + case SSL_SECOP_SIGALG_SHARED: + case SSL_SECOP_SIGALG_CHECK: + case SSL_SECOP_SIGALG_MASK: + show_nm = 0; + break; + } + if (show_nm) + BIO_printf(sdb->out, "%s=", nm); + + switch (op & SSL_SECOP_OTHER_TYPE) { + + case SSL_SECOP_OTHER_CIPHER: + BIO_puts(sdb->out, SSL_CIPHER_get_name(other)); + break; + +#ifndef OPENSSL_NO_EC + case SSL_SECOP_OTHER_CURVE: + { + const char *cname; + cname = EC_curve_nid2nist(nid); + if (cname == NULL) + cname = OBJ_nid2sn(nid); + BIO_puts(sdb->out, cname); + } + break; +#endif + case SSL_SECOP_OTHER_CERT: + { + if (cert_md) { + int sig_nid = X509_get_signature_nid(other); + + BIO_puts(sdb->out, OBJ_nid2sn(sig_nid)); + } else { + EVP_PKEY *pkey = X509_get0_pubkey(other); + + if (pkey == NULL) { + BIO_printf(sdb->out, "Public key missing"); + } else { + const char *algname = ""; + + EVP_PKEY_asn1_get0_info(NULL, NULL, NULL, NULL, + &algname, EVP_PKEY_get0_asn1(pkey)); + BIO_printf(sdb->out, "%s, bits=%d", + algname, EVP_PKEY_get_bits(pkey)); + } + } + break; + } + case SSL_SECOP_OTHER_SIGALG: + { + const unsigned char *salg = other; + const char *sname = NULL; + int raw_sig_code = (salg[0] << 8) + salg[1]; /* always big endian (msb, lsb) */ + /* raw_sig_code: signature_scheme from tls1.3, or signature_and_hash from tls1.2 */ + + if (nm != NULL) + BIO_printf(sdb->out, "%s", nm); + else + BIO_printf(sdb->out, "s_cb.c:security_callback_debug op=0x%x", op); + + sname = lookup(raw_sig_code, signature_tls13_scheme_list, NULL); + if (sname != NULL) { + BIO_printf(sdb->out, " scheme=%s", sname); + } else { + int alg_code = salg[1]; + int hash_code = salg[0]; + const char *alg_str = lookup(alg_code, signature_tls12_alg_list, NULL); + const char *hash_str = lookup(hash_code, signature_tls12_hash_list, NULL); + + if (alg_str != NULL && hash_str != NULL) + BIO_printf(sdb->out, " digest=%s, algorithm=%s", hash_str, alg_str); + else + BIO_printf(sdb->out, " scheme=unknown(0x%04x)", raw_sig_code); + } + } + + } + + if (show_bits) + BIO_printf(sdb->out, ", security bits=%d", bits); + BIO_printf(sdb->out, ": %s\n", rv ? "yes" : "no"); + return rv; +} + +void ssl_ctx_security_debug(SSL_CTX *ctx, int verbose) +{ + static security_debug_ex sdb; + + sdb.out = bio_err; + sdb.verbose = verbose; + sdb.old_cb = SSL_CTX_get_security_callback(ctx); + SSL_CTX_set_security_callback(ctx, security_callback_debug); + SSL_CTX_set0_security_ex_data(ctx, &sdb); +} + +static void keylog_callback(const SSL *ssl, const char *line) +{ + if (bio_keylog == NULL) { + BIO_printf(bio_err, "Keylog callback is invoked without valid file!\n"); + return; + } + + /* + * There might be concurrent writers to the keylog file, so we must ensure + * that the given line is written at once. + */ + BIO_printf(bio_keylog, "%s\n", line); + (void)BIO_flush(bio_keylog); +} + +int set_keylog_file(SSL_CTX *ctx, const char *keylog_file) +{ + /* Close any open files */ + BIO_free_all(bio_keylog); + bio_keylog = NULL; + + if (ctx == NULL || keylog_file == NULL) { + /* Keylogging is disabled, OK. */ + return 0; + } + + /* + * Append rather than write in order to allow concurrent modification. + * Furthermore, this preserves existing keylog files which is useful when + * the tool is run multiple times. + */ + bio_keylog = BIO_new_file(keylog_file, "a"); + if (bio_keylog == NULL) { + BIO_printf(bio_err, "Error writing keylog file %s\n", keylog_file); + return 1; + } + + /* Write a header for seekable, empty files (this excludes pipes). */ + if (BIO_tell(bio_keylog) == 0) { + BIO_puts(bio_keylog, + "# SSL/TLS secrets log file, generated by OpenSSL\n"); + (void)BIO_flush(bio_keylog); + } + SSL_CTX_set_keylog_callback(ctx, keylog_callback); + return 0; +} + +void print_ca_names(BIO *bio, SSL *s) +{ + const char *cs = SSL_is_server(s) ? "server" : "client"; + const STACK_OF(X509_NAME) *sk = SSL_get0_peer_CA_list(s); + int i; + + if (sk == NULL || sk_X509_NAME_num(sk) == 0) { + if (!SSL_is_server(s)) + BIO_printf(bio, "---\nNo %s certificate CA names sent\n", cs); + return; + } + + BIO_printf(bio, "---\nAcceptable %s certificate CA names\n",cs); + for (i = 0; i < sk_X509_NAME_num(sk); i++) { + X509_NAME_print_ex(bio, sk_X509_NAME_value(sk, i), 0, get_nameopt()); + BIO_write(bio, "\n", 1); + } +} diff --git a/apps/lib/s_socket.c b/apps/lib/s_socket.c new file mode 100644 index 000000000000..8c6020d01692 --- /dev/null +++ b/apps/lib/s_socket.c @@ -0,0 +1,462 @@ +/* + * Copyright 1995-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 + */ + +/* socket-related functions used by s_client and s_server */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <openssl/opensslconf.h> + +/* + * With IPv6, it looks like Digital has mixed up the proper order of + * recursive header file inclusion, resulting in the compiler complaining + * that u_int isn't defined, but only if _POSIX_C_SOURCE is defined, which is + * needed to have fileno() declared correctly... So let's define u_int + */ +#if defined(OPENSSL_SYS_VMS_DECC) && !defined(__U_INT) +# define __U_INT +typedef unsigned int u_int; +#endif + +#ifdef _WIN32 +# include <process.h> + +/* MSVC renamed some POSIX functions to have an underscore prefix. */ +# ifdef _MSC_VER +# define getpid _getpid +# endif +#endif + +#ifndef OPENSSL_NO_SOCK + +# include "apps.h" +# include "s_apps.h" +# include "internal/sockets.h" + +# if defined(__TANDEM) +# if defined(OPENSSL_TANDEM_FLOSS) +# include <floss.h(floss_read)> +# endif +# endif + +# include <openssl/bio.h> +# include <openssl/err.h> + +/* Keep track of our peer's address for the cookie callback */ +BIO_ADDR *ourpeer = NULL; + +/* + * init_client - helper routine to set up socket communication + * @sock: pointer to storage of resulting socket. + * @host: the host name or path (for AF_UNIX) to connect to. + * @port: the port to connect to (ignored for AF_UNIX). + * @bindhost: source host or path (for AF_UNIX). + * @bindport: source port (ignored for AF_UNIX). + * @family: desired socket family, may be AF_INET, AF_INET6, AF_UNIX or + * AF_UNSPEC + * @type: socket type, must be SOCK_STREAM or SOCK_DGRAM + * @protocol: socket protocol, e.g. IPPROTO_TCP or IPPROTO_UDP (or 0 for any) + * + * This will create a socket and use it to connect to a host:port, or if + * family == AF_UNIX, to the path found in host. + * + * If the host has more than one address, it will try them one by one until + * a successful connection is established. The resulting socket will be + * found in *sock on success, it will be given INVALID_SOCKET otherwise. + * + * Returns 1 on success, 0 on failure. + */ +int init_client(int *sock, const char *host, const char *port, + const char *bindhost, const char *bindport, + int family, int type, int protocol) +{ + BIO_ADDRINFO *res = NULL; + BIO_ADDRINFO *bindaddr = NULL; + const BIO_ADDRINFO *ai = NULL; + const BIO_ADDRINFO *bi = NULL; + int found = 0; + int ret; + + if (BIO_sock_init() != 1) + return 0; + + ret = BIO_lookup_ex(host, port, BIO_LOOKUP_CLIENT, family, type, protocol, + &res); + if (ret == 0) { + ERR_print_errors(bio_err); + return 0; + } + + if (bindhost != NULL || bindport != NULL) { + ret = BIO_lookup_ex(bindhost, bindport, BIO_LOOKUP_CLIENT, + family, type, protocol, &bindaddr); + if (ret == 0) { + ERR_print_errors (bio_err); + goto out; + } + } + + ret = 0; + for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) { + /* Admittedly, these checks are quite paranoid, we should not get + * anything in the BIO_ADDRINFO chain that we haven't + * asked for. */ + OPENSSL_assert((family == AF_UNSPEC + || family == BIO_ADDRINFO_family(ai)) + && (type == 0 || type == BIO_ADDRINFO_socktype(ai)) + && (protocol == 0 + || protocol == BIO_ADDRINFO_protocol(ai))); + + if (bindaddr != NULL) { + for (bi = bindaddr; bi != NULL; bi = BIO_ADDRINFO_next(bi)) { + if (BIO_ADDRINFO_family(bi) == BIO_ADDRINFO_family(ai)) + break; + } + if (bi == NULL) + continue; + ++found; + } + + *sock = BIO_socket(BIO_ADDRINFO_family(ai), BIO_ADDRINFO_socktype(ai), + BIO_ADDRINFO_protocol(ai), 0); + if (*sock == INVALID_SOCKET) { + /* Maybe the kernel doesn't support the socket family, even if + * BIO_lookup() added it in the returned result... + */ + continue; + } + + if (bi != NULL) { + if (!BIO_bind(*sock, BIO_ADDRINFO_address(bi), + BIO_SOCK_REUSEADDR)) { + BIO_closesocket(*sock); + *sock = INVALID_SOCKET; + break; + } + } + +#ifndef OPENSSL_NO_SCTP + if (protocol == IPPROTO_SCTP) { + /* + * For SCTP we have to set various options on the socket prior to + * connecting. This is done automatically by BIO_new_dgram_sctp(). + * We don't actually need the created BIO though so we free it again + * immediately. + */ + BIO *tmpbio = BIO_new_dgram_sctp(*sock, BIO_NOCLOSE); + + if (tmpbio == NULL) { + ERR_print_errors(bio_err); + return 0; + } + BIO_free(tmpbio); + } +#endif + + if (!BIO_connect(*sock, BIO_ADDRINFO_address(ai), + BIO_ADDRINFO_protocol(ai) == IPPROTO_TCP ? BIO_SOCK_NODELAY : 0)) { + BIO_closesocket(*sock); + *sock = INVALID_SOCKET; + continue; + } + + /* Success, don't try any more addresses */ + break; + } + + if (*sock == INVALID_SOCKET) { + if (bindaddr != NULL && !found) { + BIO_printf(bio_err, "Can't bind %saddress for %s%s%s\n", +#ifdef AF_INET6 + BIO_ADDRINFO_family(res) == AF_INET6 ? "IPv6 " : +#endif + BIO_ADDRINFO_family(res) == AF_INET ? "IPv4 " : + BIO_ADDRINFO_family(res) == AF_UNIX ? "unix " : "", + bindhost != NULL ? bindhost : "", + bindport != NULL ? ":" : "", + bindport != NULL ? bindport : ""); + ERR_clear_error(); + ret = 0; + } + ERR_print_errors(bio_err); + } else { + /* Remove any stale errors from previous connection attempts */ + ERR_clear_error(); + ret = 1; + } +out: + if (bindaddr != NULL) { + BIO_ADDRINFO_free (bindaddr); + } + BIO_ADDRINFO_free(res); + return ret; +} + +int report_server_accept(BIO *out, int asock, int with_address, int with_pid) +{ + int success = 1; + + if (BIO_printf(out, "ACCEPT") <= 0) + return 0; + if (with_address) { + union BIO_sock_info_u info; + char *hostname = NULL; + char *service = NULL; + + if ((info.addr = BIO_ADDR_new()) != NULL + && BIO_sock_info(asock, BIO_SOCK_INFO_ADDRESS, &info) + && (hostname = BIO_ADDR_hostname_string(info.addr, 1)) != NULL + && (service = BIO_ADDR_service_string(info.addr, 1)) != NULL) { + success = BIO_printf(out, + strchr(hostname, ':') == NULL + ? /* IPv4 */ " %s:%s" + : /* IPv6 */ " [%s]:%s", + hostname, service) > 0; + } else { + (void)BIO_printf(out, "unknown:error\n"); + success = 0; + } + OPENSSL_free(hostname); + OPENSSL_free(service); + BIO_ADDR_free(info.addr); + } + if (with_pid) + success = success && BIO_printf(out, " PID=%d", getpid()) > 0; + success = success && BIO_printf(out, "\n") > 0; + (void)BIO_flush(out); + + return success; +} + +/* + * do_server - helper routine to perform a server operation + * @accept_sock: pointer to storage of resulting socket. + * @host: the host name or path (for AF_UNIX) to connect to. + * @port: the port to connect to (ignored for AF_UNIX). + * @family: desired socket family, may be AF_INET, AF_INET6, AF_UNIX or + * AF_UNSPEC + * @type: socket type, must be SOCK_STREAM or SOCK_DGRAM + * @cb: pointer to a function that receives the accepted socket and + * should perform the communication with the connecting client. + * @context: pointer to memory that's passed verbatim to the cb function. + * @naccept: number of times an incoming connect should be accepted. If -1, + * unlimited number. + * + * This will create a socket and use it to listen to a host:port, or if + * family == AF_UNIX, to the path found in host, then start accepting + * incoming connections and run cb on the resulting socket. + * + * 0 on failure, something other on success. + */ +int do_server(int *accept_sock, const char *host, const char *port, + int family, int type, int protocol, do_server_cb cb, + unsigned char *context, int naccept, BIO *bio_s_out) +{ + int asock = 0; + int sock; + int i; + BIO_ADDRINFO *res = NULL; + const BIO_ADDRINFO *next; + int sock_family, sock_type, sock_protocol, sock_port; + const BIO_ADDR *sock_address; + int sock_family_fallback = AF_UNSPEC; + const BIO_ADDR *sock_address_fallback = NULL; + int sock_options = BIO_SOCK_REUSEADDR; + int ret = 0; + + if (BIO_sock_init() != 1) + return 0; + + if (!BIO_lookup_ex(host, port, BIO_LOOKUP_SERVER, family, type, protocol, + &res)) { + ERR_print_errors(bio_err); + return 0; + } + + /* Admittedly, these checks are quite paranoid, we should not get + * anything in the BIO_ADDRINFO chain that we haven't asked for */ + OPENSSL_assert((family == AF_UNSPEC || family == BIO_ADDRINFO_family(res)) + && (type == 0 || type == BIO_ADDRINFO_socktype(res)) + && (protocol == 0 || protocol == BIO_ADDRINFO_protocol(res))); + + sock_family = BIO_ADDRINFO_family(res); + sock_type = BIO_ADDRINFO_socktype(res); + sock_protocol = BIO_ADDRINFO_protocol(res); + sock_address = BIO_ADDRINFO_address(res); + next = BIO_ADDRINFO_next(res); +#ifdef AF_INET6 + if (sock_family == AF_INET6) + sock_options |= BIO_SOCK_V6_ONLY; + if (next != NULL + && BIO_ADDRINFO_socktype(next) == sock_type + && BIO_ADDRINFO_protocol(next) == sock_protocol) { + if (sock_family == AF_INET + && BIO_ADDRINFO_family(next) == AF_INET6) { + /* In case AF_INET6 is returned but not supported by the + * kernel, retry with the first detected address family */ + sock_family_fallback = sock_family; + sock_address_fallback = sock_address; + sock_family = AF_INET6; + sock_address = BIO_ADDRINFO_address(next); + } else if (sock_family == AF_INET6 + && BIO_ADDRINFO_family(next) == AF_INET) { + sock_options &= ~BIO_SOCK_V6_ONLY; + } + } +#endif + + asock = BIO_socket(sock_family, sock_type, sock_protocol, 0); + if (asock == INVALID_SOCKET && sock_family_fallback != AF_UNSPEC) { + asock = BIO_socket(sock_family_fallback, sock_type, sock_protocol, 0); + sock_address = sock_address_fallback; + } + if (asock == INVALID_SOCKET + || !BIO_listen(asock, sock_address, sock_options)) { + BIO_ADDRINFO_free(res); + ERR_print_errors(bio_err); + if (asock != INVALID_SOCKET) + BIO_closesocket(asock); + goto end; + } + +#ifndef OPENSSL_NO_SCTP + if (protocol == IPPROTO_SCTP) { + /* + * For SCTP we have to set various options on the socket prior to + * accepting. This is done automatically by BIO_new_dgram_sctp(). + * We don't actually need the created BIO though so we free it again + * immediately. + */ + BIO *tmpbio = BIO_new_dgram_sctp(asock, BIO_NOCLOSE); + + if (tmpbio == NULL) { + BIO_closesocket(asock); + ERR_print_errors(bio_err); + goto end; + } + BIO_free(tmpbio); + } +#endif + + sock_port = BIO_ADDR_rawport(sock_address); + + BIO_ADDRINFO_free(res); + res = NULL; + + if (!report_server_accept(bio_s_out, asock, sock_port == 0, 0)) { + BIO_closesocket(asock); + ERR_print_errors(bio_err); + goto end; + } + + if (accept_sock != NULL) + *accept_sock = asock; + for (;;) { + char sink[64]; + struct timeval timeout; + fd_set readfds; + + if (type == SOCK_STREAM) { + BIO_ADDR_free(ourpeer); + ourpeer = BIO_ADDR_new(); + if (ourpeer == NULL) { + BIO_closesocket(asock); + ERR_print_errors(bio_err); + goto end; + } + do { + sock = BIO_accept_ex(asock, ourpeer, 0); + } while (sock < 0 && BIO_sock_should_retry(sock)); + if (sock < 0) { + ERR_print_errors(bio_err); + BIO_closesocket(asock); + break; + } + + if (naccept != -1) + naccept--; + if (naccept == 0) + BIO_closesocket(asock); + + BIO_set_tcp_ndelay(sock, 1); + i = (*cb)(sock, type, protocol, context); + + /* + * If we ended with an alert being sent, but still with data in the + * network buffer to be read, then calling BIO_closesocket() will + * result in a TCP-RST being sent. On some platforms (notably + * Windows) then this will result in the peer immediately abandoning + * the connection including any buffered alert data before it has + * had a chance to be read. Shutting down the sending side first, + * and then closing the socket sends TCP-FIN first followed by + * TCP-RST. This seems to allow the peer to read the alert data. + */ + shutdown(sock, 1); /* SHUT_WR */ + /* + * We just said we have nothing else to say, but it doesn't mean + * that the other side has nothing. It's even recommended to + * consume incoming data. [In testing context this ensures that + * alerts are passed on...] + */ + timeout.tv_sec = 0; + timeout.tv_usec = 500000; /* some extreme round-trip */ + do { + FD_ZERO(&readfds); + openssl_fdset(sock, &readfds); + } while (select(sock + 1, &readfds, NULL, NULL, &timeout) > 0 + && readsocket(sock, sink, sizeof(sink)) > 0); + + BIO_closesocket(sock); + } else { + if (naccept != -1) + naccept--; + + i = (*cb)(asock, type, protocol, context); + } + + if (i < 0 || naccept == 0) { + BIO_closesocket(asock); + ret = i; + break; + } + } + end: +# ifdef AF_UNIX + if (family == AF_UNIX) + unlink(host); +# endif + BIO_ADDR_free(ourpeer); + ourpeer = NULL; + return ret; +} + +void do_ssl_shutdown(SSL *ssl) +{ + int ret; + + do { + /* We only do unidirectional shutdown */ + ret = SSL_shutdown(ssl); + if (ret < 0) { + switch (SSL_get_error(ssl, ret)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_ASYNC: + case SSL_ERROR_WANT_ASYNC_JOB: + /* We just do busy waiting. Nothing clever */ + continue; + } + ret = 0; + } + } while (ret < 0); +} + +#endif /* OPENSSL_NO_SOCK */ diff --git a/apps/lib/tlssrp_depr.c b/apps/lib/tlssrp_depr.c new file mode 100644 index 000000000000..91c19b096e9a --- /dev/null +++ b/apps/lib/tlssrp_depr.c @@ -0,0 +1,231 @@ +/* + * Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 2005 Nokia. 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 + */ + +/* + * This file is to enable backwards compatibility for the SRP features of + * s_client, s_server and ciphers. All of those features are deprecated and will + * eventually disappear. In the meantime, to continue to support them, we + * need to access deprecated SRP APIs. + */ +#define OPENSSL_SUPPRESS_DEPRECATED + +#include <openssl/bn.h> +#include <openssl/bio.h> +#include <openssl/ssl.h> +#include <openssl/srp.h> +#include "apps_ui.h" +#include "apps.h" +#include "s_apps.h" + +static int srp_Verify_N_and_g(const BIGNUM *N, const BIGNUM *g) +{ + BN_CTX *bn_ctx = BN_CTX_new(); + BIGNUM *p = BN_new(); + BIGNUM *r = BN_new(); + int ret = + g != NULL && N != NULL && bn_ctx != NULL && BN_is_odd(N) && + BN_check_prime(N, bn_ctx, NULL) == 1 && + p != NULL && BN_rshift1(p, N) && + /* p = (N-1)/2 */ + BN_check_prime(p, bn_ctx, NULL) == 1 && + r != NULL && + /* verify g^((N-1)/2) == -1 (mod N) */ + BN_mod_exp(r, g, p, N, bn_ctx) && + BN_add_word(r, 1) && BN_cmp(r, N) == 0; + + BN_free(r); + BN_free(p); + BN_CTX_free(bn_ctx); + return ret; +} + +/*- + * This callback is used here for two purposes: + * - extended debugging + * - making some primality tests for unknown groups + * The callback is only called for a non default group. + * + * An application does not need the call back at all if + * only the standard groups are used. In real life situations, + * client and server already share well known groups, + * thus there is no need to verify them. + * Furthermore, in case that a server actually proposes a group that + * is not one of those defined in RFC 5054, it is more appropriate + * to add the group to a static list and then compare since + * primality tests are rather cpu consuming. + */ + +static int ssl_srp_verify_param_cb(SSL *s, void *arg) +{ + SRP_ARG *srp_arg = (SRP_ARG *)arg; + BIGNUM *N = NULL, *g = NULL; + + if (((N = SSL_get_srp_N(s)) == NULL) || ((g = SSL_get_srp_g(s)) == NULL)) + return 0; + if (srp_arg->debug || srp_arg->msg || srp_arg->amp == 1) { + BIO_printf(bio_err, "SRP parameters:\n"); + BIO_printf(bio_err, "\tN="); + BN_print(bio_err, N); + BIO_printf(bio_err, "\n\tg="); + BN_print(bio_err, g); + BIO_printf(bio_err, "\n"); + } + + if (SRP_check_known_gN_param(g, N)) + return 1; + + if (srp_arg->amp == 1) { + if (srp_arg->debug) + BIO_printf(bio_err, + "SRP param N and g are not known params, going to check deeper.\n"); + + /* + * The srp_moregroups is a real debugging feature. Implementors + * should rather add the value to the known ones. The minimal size + * has already been tested. + */ + if (BN_num_bits(g) <= BN_BITS && srp_Verify_N_and_g(N, g)) + return 1; + } + BIO_printf(bio_err, "SRP param N and g rejected.\n"); + return 0; +} + +#define PWD_STRLEN 1024 + +static char *ssl_give_srp_client_pwd_cb(SSL *s, void *arg) +{ + SRP_ARG *srp_arg = (SRP_ARG *)arg; + char *pass = app_malloc(PWD_STRLEN + 1, "SRP password buffer"); + PW_CB_DATA cb_tmp; + int l; + + cb_tmp.password = (char *)srp_arg->srppassin; + cb_tmp.prompt_info = "SRP user"; + if ((l = password_callback(pass, PWD_STRLEN, 0, &cb_tmp)) < 0) { + BIO_printf(bio_err, "Can't read Password\n"); + OPENSSL_free(pass); + return NULL; + } + *(pass + l) = '\0'; + + return pass; +} + +int set_up_srp_arg(SSL_CTX *ctx, SRP_ARG *srp_arg, int srp_lateuser, int c_msg, + int c_debug) +{ + if (!srp_lateuser && !SSL_CTX_set_srp_username(ctx, srp_arg->srplogin)) { + BIO_printf(bio_err, "Unable to set SRP username\n"); + return 0; + } + srp_arg->msg = c_msg; + srp_arg->debug = c_debug; + SSL_CTX_set_srp_cb_arg(ctx, &srp_arg); + SSL_CTX_set_srp_client_pwd_callback(ctx, ssl_give_srp_client_pwd_cb); + SSL_CTX_set_srp_strength(ctx, srp_arg->strength); + if (c_msg || c_debug || srp_arg->amp == 0) + SSL_CTX_set_srp_verify_param_callback(ctx, ssl_srp_verify_param_cb); + + return 1; +} + +static char *dummy_srp(SSL *ssl, void *arg) +{ + return ""; +} + +void set_up_dummy_srp(SSL_CTX *ctx) +{ + SSL_CTX_set_srp_client_pwd_callback(ctx, dummy_srp); +} + +/* + * This callback pretends to require some asynchronous logic in order to + * obtain a verifier. When the callback is called for a new connection we + * return with a negative value. This will provoke the accept etc to return + * with an LOOKUP_X509. The main logic of the reinvokes the suspended call + * (which would normally occur after a worker has finished) and we set the + * user parameters. + */ +static int ssl_srp_server_param_cb(SSL *s, int *ad, void *arg) +{ + srpsrvparm *p = (srpsrvparm *) arg; + int ret = SSL3_AL_FATAL; + + if (p->login == NULL && p->user == NULL) { + p->login = SSL_get_srp_username(s); + BIO_printf(bio_err, "SRP username = \"%s\"\n", p->login); + return -1; + } + + if (p->user == NULL) { + BIO_printf(bio_err, "User %s doesn't exist\n", p->login); + goto err; + } + + if (SSL_set_srp_server_param + (s, p->user->N, p->user->g, p->user->s, p->user->v, + p->user->info) < 0) { + *ad = SSL_AD_INTERNAL_ERROR; + goto err; + } + BIO_printf(bio_err, + "SRP parameters set: username = \"%s\" info=\"%s\" \n", + p->login, p->user->info); + ret = SSL_ERROR_NONE; + + err: + SRP_user_pwd_free(p->user); + p->user = NULL; + p->login = NULL; + return ret; +} + +int set_up_srp_verifier_file(SSL_CTX *ctx, srpsrvparm *srp_callback_parm, + char *srpuserseed, char *srp_verifier_file) +{ + int ret; + + srp_callback_parm->vb = SRP_VBASE_new(srpuserseed); + srp_callback_parm->user = NULL; + srp_callback_parm->login = NULL; + + if (srp_callback_parm->vb == NULL) { + BIO_printf(bio_err, "Failed to initialize SRP verifier file \n"); + return 0; + } + if ((ret = + SRP_VBASE_init(srp_callback_parm->vb, + srp_verifier_file)) != SRP_NO_ERROR) { + BIO_printf(bio_err, + "Cannot initialize SRP verifier file \"%s\":ret=%d\n", + srp_verifier_file, ret); + return 0; + } + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, verify_callback); + SSL_CTX_set_srp_cb_arg(ctx, &srp_callback_parm); + SSL_CTX_set_srp_username_callback(ctx, ssl_srp_server_param_cb); + + return 1; +} + +void lookup_srp_user(srpsrvparm *srp_callback_parm, BIO *bio_s_out) +{ + SRP_user_pwd_free(srp_callback_parm->user); + srp_callback_parm->user = SRP_VBASE_get1_by_user(srp_callback_parm->vb, + srp_callback_parm->login); + + if (srp_callback_parm->user != NULL) + BIO_printf(bio_s_out, "LOOKUP done %s\n", + srp_callback_parm->user->info); + else + BIO_printf(bio_s_out, "LOOKUP not successful\n"); +} diff --git a/apps/lib/vms_decc_argv.c b/apps/lib/vms_decc_argv.c new file mode 100644 index 000000000000..031e5afdeca0 --- /dev/null +++ b/apps/lib/vms_decc_argv.c @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2021 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 <stdlib.h> +#include <openssl/crypto.h> +#include "platform.h" /* for copy_argv() */ + +char **newargv = NULL; + +static void cleanup_argv(void) +{ + OPENSSL_free(newargv); + newargv = NULL; +} + +char **copy_argv(int *argc, char *argv[]) +{ + /*- + * The note below is for historical purpose. On VMS now we always + * copy argv "safely." + * + * 2011-03-22 SMS. + * If we have 32-bit pointers everywhere, then we're safe, and + * we bypass this mess, as on non-VMS systems. + * Problem 1: Compaq/HP C before V7.3 always used 32-bit + * pointers for argv[]. + * Fix 1: For a 32-bit argv[], when we're using 64-bit pointers + * everywhere else, we always allocate and use a 64-bit + * duplicate of argv[]. + * Problem 2: Compaq/HP C V7.3 (Alpha, IA64) before ECO1 failed + * to NULL-terminate a 64-bit argv[]. (As this was written, the + * compiler ECO was available only on IA64.) + * Fix 2: Unless advised not to (VMS_TRUST_ARGV), we test a + * 64-bit argv[argc] for NULL, and, if necessary, use a + * (properly) NULL-terminated (64-bit) duplicate of argv[]. + * The same code is used in either case to duplicate argv[]. + * Some of these decisions could be handled in preprocessing, + * but the code tends to get even uglier, and the penalty for + * deciding at compile- or run-time is tiny. + */ + + int i, count = *argc; + char **p = newargv; + + cleanup_argv(); + + /* + * We purposefully use OPENSSL_malloc() rather than app_malloc() here, + * to avoid symbol name clashes in test programs that would otherwise + * get them when linking with all of libapps.a. + * See comment in test/build.info. + */ + newargv = OPENSSL_malloc(sizeof(*newargv) * (count + 1)); + if (newargv == NULL) + return NULL; + + /* Register automatic cleanup on first use */ + if (p == NULL) + OPENSSL_atexit(cleanup_argv); + + for (i = 0; i < count; i++) + newargv[i] = argv[i]; + newargv[i] = NULL; + *argc = i; + return newargv; +} diff --git a/apps/lib/vms_term_sock.c b/apps/lib/vms_term_sock.c new file mode 100644 index 000000000000..1a413376b20b --- /dev/null +++ b/apps/lib/vms_term_sock.c @@ -0,0 +1,591 @@ +/* + * Copyright 2016-2022 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 2016 VMS Software, Inc. 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 + */ + +#ifdef __VMS +# define OPENSSL_SYS_VMS +# pragma message disable DOLLARID + + +# include <openssl/opensslconf.h> + +# if !defined(_POSIX_C_SOURCE) && defined(OPENSSL_SYS_VMS) +/* + * On VMS, you need to define this to get the declaration of fileno(). The + * value 2 is to make sure no function defined in POSIX-2 is left undefined. + */ +# define _POSIX_C_SOURCE 2 +# endif + +# include <stdio.h> + +# undef _POSIX_C_SOURCE + +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <inet.h> +# include <unistd.h> +# include <string.h> +# include <errno.h> +# include <starlet.h> +# include <iodef.h> +# ifdef __alpha +# include <iosbdef.h> +# else +typedef struct _iosb { /* Copied from IOSBDEF.H for Alpha */ +# pragma __nomember_alignment + __union { + __struct { + unsigned short int iosb$w_status; /* Final I/O status */ + __union { + __struct { /* 16-bit byte count variant */ + unsigned short int iosb$w_bcnt; /* 16-bit byte count */ + __union { + unsigned int iosb$l_dev_depend; /* 32-bit device dependent info */ + unsigned int iosb$l_pid; /* 32-bit pid */ + } iosb$r_l; + } iosb$r_bcnt_16; + __struct { /* 32-bit byte count variant */ + unsigned int iosb$l_bcnt; /* 32-bit byte count (unaligned) */ + unsigned short int iosb$w_dev_depend_high; /* 16-bit device dependent info */ + } iosb$r_bcnt_32; + } iosb$r_devdepend; + } iosb$r_io_64; + __struct { + __union { + unsigned int iosb$l_getxxi_status; /* Final GETxxI status */ + unsigned int iosb$l_reg_status; /* Final $Registry status */ + } iosb$r_l_status; + unsigned int iosb$l_reserved; /* Reserved field */ + } iosb$r_get_64; + } iosb$r_io_get; +} IOSB; + +# if !defined(__VAXC) +# define iosb$w_status iosb$r_io_get.iosb$r_io_64.iosb$w_status +# define iosb$w_bcnt iosb$r_io_get.iosb$r_io_64.iosb$r_devdepend.iosb$r_bcnt_16.iosb$w_bcnt +# define iosb$r_l iosb$r_io_get.iosb$r_io_64.iosb$r_devdepend.iosb$r_bcnt_16.iosb$r_l +# define iosb$l_dev_depend iosb$r_l.iosb$l_dev_depend +# define iosb$l_pid iosb$r_l.iosb$l_pid +# define iosb$l_bcnt iosb$r_io_get.iosb$r_io_64.iosb$r_devdepend.iosb$r_bcnt_32.iosb$l_bcnt +# define iosb$w_dev_depend_high iosb$r_io_get.iosb$r_io_64.iosb$r_devdepend.iosb$r_bcnt_32.iosb$w_dev_depend_high +# define iosb$l_getxxi_status iosb$r_io_get.iosb$r_get_64.iosb$r_l_status.iosb$l_getxxi_status +# define iosb$l_reg_status iosb$r_io_get.iosb$r_get_64.iosb$r_l_status.iosb$l_reg_status +# endif /* #if !defined(__VAXC) */ + +# endif /* End of IOSBDEF */ + +# include <efndef.h> +# include <stdlib.h> +# include <ssdef.h> +# include <time.h> +# include <stdarg.h> +# include <descrip.h> + +# include "vms_term_sock.h" + +# ifdef __alpha +static struct _iosb TerminalDeviceIosb; +# else +IOSB TerminalDeviceIosb; +# endif + +static char TerminalDeviceBuff[255 + 2]; +static int TerminalSocketPair[2] = {0, 0}; +static unsigned short TerminalDeviceChan = 0; + +static int CreateSocketPair (int, int, int, int *); +static void SocketPairTimeoutAst (int); +static int TerminalDeviceAst (int); +static void LogMessage (char *, ...); + +/* +** Socket Pair Timeout Value (must be 0-59 seconds) +*/ +# define SOCKET_PAIR_TIMEOUT_VALUE 20 + +/* +** Socket Pair Timeout Block which is passed to timeout AST +*/ +typedef struct _SocketPairTimeoutBlock { + unsigned short SockChan1; + unsigned short SockChan2; +} SPTB; + +# ifdef TERM_SOCK_TEST + +/*----------------------------------------------------------------------------*/ +/* */ +/*----------------------------------------------------------------------------*/ +int main (int argc, char *argv[], char *envp[]) +{ + char TermBuff[80]; + int TermSock, + status, + len; + + LogMessage ("Enter 'q' or 'Q' to quit ..."); + while (OPENSSL_strcasecmp (TermBuff, "Q")) { + /* + ** Create the terminal socket + */ + status = TerminalSocket (TERM_SOCK_CREATE, &TermSock); + if (status != TERM_SOCK_SUCCESS) + exit (1); + + /* + ** Process the terminal input + */ + LogMessage ("Waiting on terminal I/O ...\n"); + len = recv (TermSock, TermBuff, sizeof(TermBuff), 0) ; + TermBuff[len] = '\0'; + LogMessage ("Received terminal I/O [%s]", TermBuff); + + /* + ** Delete the terminal socket + */ + status = TerminalSocket (TERM_SOCK_DELETE, &TermSock); + if (status != TERM_SOCK_SUCCESS) + exit (1); + } + + return 1; + +} +# endif + +/*----------------------------------------------------------------------------*/ +/* */ +/*----------------------------------------------------------------------------*/ +int TerminalSocket (int FunctionCode, int *ReturnSocket) +{ + int status; + $DESCRIPTOR (TerminalDeviceDesc, "SYS$COMMAND"); + + /* + ** Process the requested function code + */ + switch (FunctionCode) { + case TERM_SOCK_CREATE: + /* + ** Create a socket pair + */ + status = CreateSocketPair (AF_INET, SOCK_STREAM, 0, TerminalSocketPair); + if (status == -1) { + LogMessage ("TerminalSocket: CreateSocketPair () - %08X", status); + if (TerminalSocketPair[0]) + close (TerminalSocketPair[0]); + if (TerminalSocketPair[1]) + close (TerminalSocketPair[1]); + return TERM_SOCK_FAILURE; + } + + /* + ** Assign a channel to the terminal device + */ + status = sys$assign (&TerminalDeviceDesc, + &TerminalDeviceChan, + 0, 0, 0); + if (! (status & 1)) { + LogMessage ("TerminalSocket: SYS$ASSIGN () - %08X", status); + close (TerminalSocketPair[0]); + close (TerminalSocketPair[1]); + return TERM_SOCK_FAILURE; + } + + /* + ** Queue an async IO to the terminal device + */ + status = sys$qio (EFN$C_ENF, + TerminalDeviceChan, + IO$_READVBLK, + &TerminalDeviceIosb, + TerminalDeviceAst, + 0, + TerminalDeviceBuff, + sizeof(TerminalDeviceBuff) - 2, + 0, 0, 0, 0); + if (! (status & 1)) { + LogMessage ("TerminalSocket: SYS$QIO () - %08X", status); + close (TerminalSocketPair[0]); + close (TerminalSocketPair[1]); + return TERM_SOCK_FAILURE; + } + + /* + ** Return the input side of the socket pair + */ + *ReturnSocket = TerminalSocketPair[1]; + break; + + case TERM_SOCK_DELETE: + /* + ** Cancel any pending IO on the terminal channel + */ + status = sys$cancel (TerminalDeviceChan); + if (! (status & 1)) { + LogMessage ("TerminalSocket: SYS$CANCEL () - %08X", status); + close (TerminalSocketPair[0]); + close (TerminalSocketPair[1]); + return TERM_SOCK_FAILURE; + } + + /* + ** Deassign the terminal channel + */ + status = sys$dassgn (TerminalDeviceChan); + if (! (status & 1)) { + LogMessage ("TerminalSocket: SYS$DASSGN () - %08X", status); + close (TerminalSocketPair[0]); + close (TerminalSocketPair[1]); + return TERM_SOCK_FAILURE; + } + + /* + ** Close the terminal socket pair + */ + close (TerminalSocketPair[0]); + close (TerminalSocketPair[1]); + + /* + ** Return the initialized socket + */ + *ReturnSocket = 0; + break; + + default: + /* + ** Invalid function code + */ + LogMessage ("TerminalSocket: Invalid Function Code - %d", FunctionCode); + return TERM_SOCK_FAILURE; + break; + } + + /* + ** Return success + */ + return TERM_SOCK_SUCCESS; + +} + +/*----------------------------------------------------------------------------*/ +/* */ +/*----------------------------------------------------------------------------*/ +static int CreateSocketPair (int SocketFamily, + int SocketType, + int SocketProtocol, + int *SocketPair) +{ + struct dsc$descriptor AscTimeDesc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL}; + static const char* LocalHostAddr = {"127.0.0.1"}; + unsigned short TcpAcceptChan = 0, + TcpDeviceChan = 0; + unsigned long BinTimeBuff[2]; + struct sockaddr_in sin; + char AscTimeBuff[32]; + short LocalHostPort; + int status; + unsigned int slen; + +# ifdef __alpha + struct _iosb iosb; +# else + IOSB iosb; +# endif + + int SockDesc1 = 0, + SockDesc2 = 0; + SPTB sptb; + $DESCRIPTOR (TcpDeviceDesc, "TCPIP$DEVICE"); + + /* + ** Create a socket + */ + SockDesc1 = socket (SocketFamily, SocketType, 0); + if (SockDesc1 < 0) { + LogMessage ("CreateSocketPair: socket () - %d", errno); + return -1; + } + + /* + ** Initialize the socket information + */ + slen = sizeof(sin); + memset ((char *) &sin, 0, slen); + sin.sin_family = SocketFamily; + sin.sin_addr.s_addr = inet_addr (LocalHostAddr); + sin.sin_port = 0; + + /* + ** Bind the socket to the local IP + */ + status = bind (SockDesc1, (struct sockaddr *) &sin, slen); + if (status < 0) { + LogMessage ("CreateSocketPair: bind () - %d", errno); + close (SockDesc1); + return -1; + } + + /* + ** Get the socket name so we can save the port number + */ + status = getsockname (SockDesc1, (struct sockaddr *) &sin, &slen); + if (status < 0) { + LogMessage ("CreateSocketPair: getsockname () - %d", errno); + close (SockDesc1); + return -1; + } else + LocalHostPort = sin.sin_port; + + /* + ** Setup a listen for the socket + */ + listen (SockDesc1, 5); + + /* + ** Get the binary (64-bit) time of the specified timeout value + */ + BIO_snprintf(AscTimeBuff, sizeof(AscTimeBuff), "0 0:0:%02d.00", SOCKET_PAIR_TIMEOUT_VALUE); + AscTimeDesc.dsc$w_length = strlen (AscTimeBuff); + AscTimeDesc.dsc$a_pointer = AscTimeBuff; + status = sys$bintim (&AscTimeDesc, BinTimeBuff); + if (! (status & 1)) { + LogMessage ("CreateSocketPair: SYS$BINTIM () - %08X", status); + close (SockDesc1); + return -1; + } + + /* + ** Assign another channel to the TCP/IP device for the accept. + ** This is the channel that ends up being connected to. + */ + status = sys$assign (&TcpDeviceDesc, &TcpDeviceChan, 0, 0, 0); + if (! (status & 1)) { + LogMessage ("CreateSocketPair: SYS$ASSIGN () - %08X", status); + close (SockDesc1); + return -1; + } + + /* + ** Get the channel of the first socket for the accept + */ + TcpAcceptChan = decc$get_sdc (SockDesc1); + + /* + ** Perform the accept using $QIO so we can do this asynchronously + */ + status = sys$qio (EFN$C_ENF, + TcpAcceptChan, + IO$_ACCESS | IO$M_ACCEPT, + &iosb, + 0, 0, 0, 0, 0, + &TcpDeviceChan, + 0, 0); + if (! (status & 1)) { + LogMessage ("CreateSocketPair: SYS$QIO () - %08X", status); + close (SockDesc1); + sys$dassgn (TcpDeviceChan); + return -1; + } + + /* + ** Create the second socket to do the connect + */ + SockDesc2 = socket (SocketFamily, SocketType, 0); + if (SockDesc2 < 0) { + LogMessage ("CreateSocketPair: socket () - %d", errno); + sys$cancel (TcpAcceptChan); + close (SockDesc1); + sys$dassgn (TcpDeviceChan); + return (-1) ; + } + + /* + ** Setup the Socket Pair Timeout Block + */ + sptb.SockChan1 = TcpAcceptChan; + sptb.SockChan2 = decc$get_sdc (SockDesc2); + + /* + ** Before we block on the connect, set a timer that can cancel I/O on our + ** two sockets if it never connects. + */ + status = sys$setimr (EFN$C_ENF, + BinTimeBuff, + SocketPairTimeoutAst, + &sptb, + 0); + if (! (status & 1)) { + LogMessage ("CreateSocketPair: SYS$SETIMR () - %08X", status); + sys$cancel (TcpAcceptChan); + close (SockDesc1); + close (SockDesc2); + sys$dassgn (TcpDeviceChan); + return -1; + } + + /* + ** Now issue the connect + */ + memset ((char *) &sin, 0, sizeof(sin)) ; + sin.sin_family = SocketFamily; + sin.sin_addr.s_addr = inet_addr (LocalHostAddr) ; + sin.sin_port = LocalHostPort ; + + status = connect (SockDesc2, (struct sockaddr *) &sin, sizeof(sin)); + if (status < 0 ) { + LogMessage ("CreateSocketPair: connect () - %d", errno); + sys$cantim (&sptb, 0); + sys$cancel (TcpAcceptChan); + close (SockDesc1); + close (SockDesc2); + sys$dassgn (TcpDeviceChan); + return -1; + } + + /* + ** Wait for the asynch $QIO to finish. Note that if the I/O was aborted + ** (SS$_ABORT), then we probably canceled it from the AST routine - so log + ** a timeout. + */ + status = sys$synch (EFN$C_ENF, &iosb); + if (! (iosb.iosb$w_status & 1)) { + if (iosb.iosb$w_status == SS$_ABORT) + LogMessage ("CreateSocketPair: SYS$QIO(iosb) timeout"); + else { + LogMessage ("CreateSocketPair: SYS$QIO(iosb) - %d", + iosb.iosb$w_status); + sys$cantim (&sptb, 0); + } + close (SockDesc1); + close (SockDesc2); + sys$dassgn (TcpDeviceChan); + return -1; + } + + /* + ** Here we're successfully connected, so cancel the timer, convert the + ** I/O channel to a socket fd, close the listener socket and return the + ** connected pair. + */ + sys$cantim (&sptb, 0); + + close (SockDesc1) ; + SocketPair[0] = SockDesc2 ; + SocketPair[1] = socket_fd (TcpDeviceChan); + + return (0) ; + +} + +/*----------------------------------------------------------------------------*/ +/* */ +/*----------------------------------------------------------------------------*/ +static void SocketPairTimeoutAst (int astparm) +{ + SPTB *sptb = (SPTB *) astparm; + + sys$cancel (sptb->SockChan2); /* Cancel the connect() */ + sys$cancel (sptb->SockChan1); /* Cancel the accept() */ + + return; + +} + +/*----------------------------------------------------------------------------*/ +/* */ +/*----------------------------------------------------------------------------*/ +static int TerminalDeviceAst (int astparm) +{ + int status; + + /* + ** Terminate the terminal buffer + */ + TerminalDeviceBuff[TerminalDeviceIosb.iosb$w_bcnt] = '\0'; + strcat (TerminalDeviceBuff, "\n"); + + /* + ** Send the data read from the terminal device through the socket pair + */ + send (TerminalSocketPair[0], TerminalDeviceBuff, + TerminalDeviceIosb.iosb$w_bcnt + 1, 0); + + /* + ** Queue another async IO to the terminal device + */ + status = sys$qio (EFN$C_ENF, + TerminalDeviceChan, + IO$_READVBLK, + &TerminalDeviceIosb, + TerminalDeviceAst, + 0, + TerminalDeviceBuff, + sizeof(TerminalDeviceBuff) - 1, + 0, 0, 0, 0); + + /* + ** Return status + */ + return status; + +} + +/*----------------------------------------------------------------------------*/ +/* */ +/*----------------------------------------------------------------------------*/ +static void LogMessage (char *msg, ...) +{ + char *Month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + static unsigned int pid = 0; + va_list args; + time_t CurTime; + struct tm *LocTime; + char MsgBuff[256]; + + /* + ** Get the process pid + */ + if (pid == 0) + pid = getpid (); + + /* + ** Convert the current time into local time + */ + CurTime = time (NULL); + LocTime = localtime (&CurTime); + + /* + ** Format the message buffer + */ + BIO_snprintf(MsgBuff, sizeof(MsgBuff), "%02d-%s-%04d %02d:%02d:%02d [%08X] %s\n", + LocTime->tm_mday, Month[LocTime->tm_mon], + (LocTime->tm_year + 1900), LocTime->tm_hour, LocTime->tm_min, + LocTime->tm_sec, pid, msg); + + /* + ** Get any variable arguments and add them to the print of the message + ** buffer + */ + va_start (args, msg); + vfprintf (stderr, MsgBuff, args); + va_end (args); + + /* + ** Flush standard error output + */ + fsync (fileno (stderr)); + + return; + +} +#endif diff --git a/apps/lib/win32_init.c b/apps/lib/win32_init.c new file mode 100644 index 000000000000..6d2be0c62942 --- /dev/null +++ b/apps/lib/win32_init.c @@ -0,0 +1,307 @@ +/* + * Copyright 2016-2020 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 <windows.h> +#include <stdlib.h> +#include <string.h> +#include <malloc.h> + +#if defined(CP_UTF8) + +static UINT saved_cp; +static int newargc; +static char **newargv; + +static void cleanup(void) +{ + int i; + + SetConsoleOutputCP(saved_cp); + + for (i = 0; i < newargc; i++) + free(newargv[i]); + + free(newargv); +} + +/* + * Incrementally [re]allocate newargv and keep it NULL-terminated. + */ +static int validate_argv(int argc) +{ + static int size = 0; + + if (argc >= size) { + char **ptr; + + while (argc >= size) + size += 64; + + ptr = realloc(newargv, size * sizeof(newargv[0])); + if (ptr == NULL) + return 0; + + (newargv = ptr)[argc] = NULL; + } else { + newargv[argc] = NULL; + } + + return 1; +} + +static int process_glob(WCHAR *wstr, int wlen) +{ + int i, slash, udlen; + WCHAR saved_char; + WIN32_FIND_DATAW data; + HANDLE h; + + /* + * Note that we support wildcard characters only in filename part + * of the path, and not in directories. Windows users are used to + * this, that's why recursive glob processing is not implemented. + */ + /* + * Start by looking for last slash or backslash, ... + */ + for (slash = 0, i = 0; i < wlen; i++) + if (wstr[i] == L'/' || wstr[i] == L'\\') + slash = i + 1; + /* + * ... then look for asterisk or question mark in the file name. + */ + for (i = slash; i < wlen; i++) + if (wstr[i] == L'*' || wstr[i] == L'?') + break; + + if (i == wlen) + return 0; /* definitely not a glob */ + + saved_char = wstr[wlen]; + wstr[wlen] = L'\0'; + h = FindFirstFileW(wstr, &data); + wstr[wlen] = saved_char; + if (h == INVALID_HANDLE_VALUE) + return 0; /* not a valid glob, just pass... */ + + if (slash) + udlen = WideCharToMultiByte(CP_UTF8, 0, wstr, slash, + NULL, 0, NULL, NULL); + else + udlen = 0; + + do { + int uflen; + char *arg; + + /* + * skip over . and .. + */ + if (data.cFileName[0] == L'.') { + if ((data.cFileName[1] == L'\0') || + (data.cFileName[1] == L'.' && data.cFileName[2] == L'\0')) + continue; + } + + if (!validate_argv(newargc + 1)) + break; + + /* + * -1 below means "scan for trailing '\0' *and* count it", + * so that |uflen| covers even trailing '\0'. + */ + uflen = WideCharToMultiByte(CP_UTF8, 0, data.cFileName, -1, + NULL, 0, NULL, NULL); + + arg = malloc(udlen + uflen); + if (arg == NULL) + break; + + if (udlen) + WideCharToMultiByte(CP_UTF8, 0, wstr, slash, + arg, udlen, NULL, NULL); + + WideCharToMultiByte(CP_UTF8, 0, data.cFileName, -1, + arg + udlen, uflen, NULL, NULL); + + newargv[newargc++] = arg; + } while (FindNextFileW(h, &data)); + + CloseHandle(h); + + return 1; +} + +void win32_utf8argv(int *argc, char **argv[]) +{ + const WCHAR *wcmdline; + WCHAR *warg, *wend, *p; + int wlen, ulen, valid = 1; + char *arg; + + if (GetEnvironmentVariableW(L"OPENSSL_WIN32_UTF8", NULL, 0) == 0) + return; + + newargc = 0; + newargv = NULL; + if (!validate_argv(newargc)) + return; + + wcmdline = GetCommandLineW(); + if (wcmdline == NULL) return; + + /* + * make a copy of the command line, since we might have to modify it... + */ + wlen = wcslen(wcmdline); + p = _alloca((wlen + 1) * sizeof(WCHAR)); + wcscpy(p, wcmdline); + + while (*p != L'\0') { + int in_quote = 0; + + if (*p == L' ' || *p == L'\t') { + p++; /* skip over whitespace */ + continue; + } + + /* + * Note: because we may need to fiddle with the number of backslashes, + * the argument string is copied into itself. This is safe because + * the number of characters will never expand. + */ + warg = wend = p; + while (*p != L'\0' + && (in_quote || (*p != L' ' && *p != L'\t'))) { + switch (*p) { + case L'\\': + /* + * Microsoft documentation on how backslashes are treated + * is: + * + * + Backslashes are interpreted literally, unless they + * immediately precede a double quotation mark. + * + If an even number of backslashes is followed by a double + * quotation mark, one backslash is placed in the argv array + * for every pair of backslashes, and the double quotation + * mark is interpreted as a string delimiter. + * + If an odd number of backslashes is followed by a double + * quotation mark, one backslash is placed in the argv array + * for every pair of backslashes, and the double quotation + * mark is "escaped" by the remaining backslash, causing a + * literal double quotation mark (") to be placed in argv. + * + * Ref: https://msdn.microsoft.com/en-us/library/17w5ykft.aspx + * + * Though referred page doesn't mention it, multiple qouble + * quotes are also special. Pair of double quotes in quoted + * string is counted as single double quote. + */ + { + const WCHAR *q = p; + int i; + + while (*p == L'\\') + p++; + + if (*p == L'"') { + int i; + + for (i = (p - q) / 2; i > 0; i--) + *wend++ = L'\\'; + + /* + * if odd amount of backslashes before the quote, + * said quote is part of the argument, not a delimiter + */ + if ((p - q) % 2 == 1) + *wend++ = *p++; + } else { + for (i = p - q; i > 0; i--) + *wend++ = L'\\'; + } + } + break; + case L'"': + /* + * Without the preceding backslash (or when preceded with an + * even number of backslashes), the double quote is a simple + * string delimiter and just slightly change the parsing state + */ + if (in_quote && p[1] == L'"') + *wend++ = *p++; + else + in_quote = !in_quote; + p++; + break; + default: + /* + * Any other non-delimiter character is just taken verbatim + */ + *wend++ = *p++; + } + } + + wlen = wend - warg; + + if (wlen == 0 || !process_glob(warg, wlen)) { + if (!validate_argv(newargc + 1)) { + valid = 0; + break; + } + + ulen = 0; + if (wlen > 0) { + ulen = WideCharToMultiByte(CP_UTF8, 0, warg, wlen, + NULL, 0, NULL, NULL); + if (ulen <= 0) + continue; + } + + arg = malloc(ulen + 1); + if (arg == NULL) { + valid = 0; + break; + } + + if (wlen > 0) + WideCharToMultiByte(CP_UTF8, 0, warg, wlen, + arg, ulen, NULL, NULL); + arg[ulen] = '\0'; + + newargv[newargc++] = arg; + } + } + + if (valid) { + saved_cp = GetConsoleOutputCP(); + SetConsoleOutputCP(CP_UTF8); + + *argc = newargc; + *argv = newargv; + + atexit(cleanup); + } else if (newargv != NULL) { + int i; + + for (i = 0; i < newargc; i++) + free(newargv[i]); + + free(newargv); + + newargc = 0; + newargv = NULL; + } + + return; +} +#else +void win32_utf8argv(int *argc, char **argv[]) +{ return; } +#endif |