From bd91eb6658b176fd35b4a7df6caab98dc21ef5f3 Mon Sep 17 00:00:00 2001 From: Igor Ustinov Date: Sat, 2 Aug 2025 19:53:13 +0300 Subject: [PATCH] dgst and mac apps: Added new ways for obtaining a MAC key Resolves #24584 It is now possible to obtain a MAC key from an environment variable, a file or read it from the standard input. Reviewed-by: Dmitry Belyavskiy Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/28160) --- apps/dgst.c | 43 +++++- apps/include/apps.h | 2 + apps/lib/apps.c | 154 +++++++++++++++++++++ apps/mac.c | 14 +- doc/man1/openssl-dgst.pod.in | 62 +++++++++ doc/man1/openssl-mac.pod.in | 35 +++++ test/recipes/20-test_dgst.t | 112 ++++++++++++++- test/recipes/20-test_dgst_data/keyfile.bin | 1 + test/recipes/20-test_dgst_data/keyfile.txt | 1 + test/recipes/20-test_mac.t | 55 +++++++- test/recipes/20-test_mac_data/keyfile.dat | 1 + 11 files changed, 463 insertions(+), 17 deletions(-) create mode 100644 test/recipes/20-test_dgst_data/keyfile.bin create mode 100644 test/recipes/20-test_dgst_data/keyfile.txt create mode 100644 test/recipes/20-test_mac_data/keyfile.dat diff --git a/apps/dgst.c b/apps/dgst.c index 3257acd0c3b..07fa5fe26d2 100644 --- a/apps/dgst.c +++ b/apps/dgst.c @@ -44,7 +44,7 @@ typedef enum OPTION_choice { OPT_C, OPT_R, OPT_OUT, OPT_SIGN, OPT_PASSIN, OPT_VERIFY, OPT_PRVERIFY, OPT_SIGNATURE, OPT_KEYFORM, OPT_ENGINE, OPT_ENGINE_IMPL, OPT_HEX, OPT_BINARY, OPT_DEBUG, OPT_FIPS_FINGERPRINT, - OPT_HMAC, OPT_MAC, OPT_SIGOPT, OPT_MACOPT, OPT_XOFLEN, + OPT_HMAC, OPT_HMAC_ENV, OPT_HMAC_STDIN, OPT_MAC, OPT_SIGOPT, OPT_MACOPT, OPT_XOFLEN, OPT_DIGEST, OPT_R_ENUM, OPT_PROV_ENUM } OPTION_CHOICE; @@ -80,6 +80,8 @@ const OPTIONS dgst_options[] = { {"sigopt", OPT_SIGOPT, 's', "Signature parameter in n:v form"}, {"signature", OPT_SIGNATURE, '<', "File with signature to verify"}, {"hmac", OPT_HMAC, 's', "Create hashed MAC with key"}, + {"hmac-env", OPT_HMAC_ENV, 's', "Create hashed MAC with key from environment variable"}, + {"hmac-stdin", OPT_HMAC_STDIN, '-', "Create hashed MAC with key from stdin"}, {"mac", OPT_MAC, 's', "Create MAC (not necessarily HMAC)"}, {"macopt", OPT_MACOPT, 's', "MAC algorithm parameters in n:v form or key"}, {"", OPT_DIGEST, '-', "Any supported digest"}, @@ -107,6 +109,9 @@ int dgst_main(int argc, char **argv) const char *outfile = NULL, *keyfile = NULL, *prog = NULL; const char *sigfile = NULL; const char *md_name = NULL; + char *env_var = NULL; + char *new_opt = NULL; + char *key_from_stdin = NULL; OPTION_CHOICE o; int separator = 0, debug = 0, keyform = FORMAT_UNDEF, siglen = 0; int i, ret = EXIT_FAILURE, out_bin = -1, want_pub = 0, do_verify = 0; @@ -202,6 +207,29 @@ int dgst_main(int argc, char **argv) case OPT_HMAC: hmac_key = opt_arg(); break; + case OPT_HMAC_ENV: + env_var = opt_arg(); + hmac_key = getenv(env_var); + if (hmac_key == NULL) { + BIO_printf(bio_err, "No environment variable %s\n", env_var); + ret = EXIT_FAILURE; + goto end; + } + break; + case OPT_HMAC_STDIN: + if (key_from_stdin == NULL) + key_from_stdin = get_str_from_file(NULL); + if (key_from_stdin == NULL) { + ret = EXIT_FAILURE; + goto end; + } + if (strlen(key_from_stdin) == 0) { + BIO_printf(bio_err, "Empty key\n"); + ret = EXIT_FAILURE; + goto end; + } + hmac_key = key_from_stdin; + break; case OPT_MAC: mac_name = opt_arg(); break; @@ -212,10 +240,17 @@ int dgst_main(int argc, char **argv) goto opthelp; break; case OPT_MACOPT: + new_opt = process_additional_mac_key_arguments(opt_arg()); + if (new_opt == NULL) { + ret = EXIT_FAILURE; + goto end; + } if (!macopts) macopts = sk_OPENSSL_STRING_new_null(); - if (!macopts || !sk_OPENSSL_STRING_push(macopts, opt_arg())) + if (!macopts || !sk_OPENSSL_STRING_push(macopts, new_opt)) { + clear_free(new_opt); goto opthelp; + } break; case OPT_DIGEST: digestname = opt_unknown(); @@ -487,6 +522,8 @@ int dgst_main(int argc, char **argv) if (ret != EXIT_SUCCESS) ERR_print_errors(bio_err); OPENSSL_clear_free(buf, BUFSIZE); + if (key_from_stdin != NULL) + clear_free(key_from_stdin); BIO_free(in); OPENSSL_free(passin); BIO_free_all(out); @@ -494,7 +531,7 @@ int dgst_main(int argc, char **argv) EVP_PKEY_free(sigkey); EVP_MD_CTX_free(signctx); sk_OPENSSL_STRING_free(sigopts); - sk_OPENSSL_STRING_free(macopts); + sk_OPENSSL_STRING_pop_free(macopts, clear_free); OPENSSL_free(sigbuf); BIO_free(bmd); release_engine(e); diff --git a/apps/include/apps.h b/apps/include/apps.h index 607e2e1e90a..58e92102362 100644 --- a/apps/include/apps.h +++ b/apps/include/apps.h @@ -139,6 +139,8 @@ EVP_PKEY *load_keyparams_suppress(const char *uri, int format, int maybe_stdin, const char *keytype, const char *desc, int suppress_decode_errors); char *next_item(char *opt); /* in list separated by comma and/or space */ +char *process_additional_mac_key_arguments(const char *arg); +char *get_str_from_file(const char *filename); int load_cert_certs(const char *uri, X509 **pcert, STACK_OF(X509) **pcerts, int exclude_http, const char *pass, const char *desc, diff --git a/apps/lib/apps.c b/apps/lib/apps.c index fbb3dbf3dca..fe9519e1ef3 100644 --- a/apps/lib/apps.c +++ b/apps/lib/apps.c @@ -3513,3 +3513,157 @@ int opt_legacy_okay(void) return 0; return 1; } + +#define MAX_KEY_SIZE 2048 /* Hope nobody needs mac key longer than 2048 bytes */ + +/* + * Implementations of mac algorithms only support getting a key via the + * key and hexkey parameters. This function processes additional parameters + * for reading a key from an environment variable or from a file or stdin + * and forms a key or hexkey parameter with the read key. + * Leaves other parameters unchanged. + * Allocates a string with a new parameter and returns a pointer to this + * string, the calling code must free this string by calling OPENSSL_clear_free. + * Returns NULL in case of an error. + */ +char *process_additional_mac_key_arguments(const char *arg) +{ + static BIO *keybio = NULL; + char *val = NULL, *inbuf = NULL, *outbuf = NULL; + int total_read = 0; + int n; + char dummy; + int too_long; + + if (CHECK_AND_SKIP_PREFIX(arg, "keyenv:")) { + if (strlen(arg) == 0) { + BIO_printf(bio_err, "Empty environment variable name\n"); + return NULL; + } + val = getenv(arg); + if (val == NULL) { + BIO_printf(bio_err, "No environment variable %s\n", arg); + return NULL; + } + outbuf = app_malloc(strlen("key:") + strlen(val) + 1, "MACOPT KEYENV"); + strcpy(outbuf, "key:"); + strcat(outbuf, val); + return outbuf; + } + + if (CHECK_AND_SKIP_PREFIX(arg, "keyenvhex:")) { + if (strlen(arg) == 0) { + BIO_printf(bio_err, "Empty environment variable name\n"); + return NULL; + } + val = getenv(arg); + if (val == NULL) { + BIO_printf(bio_err, "No environment variable %s\n", arg); + return NULL; + } + outbuf = app_malloc(strlen("hexkey:") + strlen(val) + 1, "MACOPT KEYENVHEX"); + strcpy(outbuf, "hexkey:"); + strcat(outbuf, val); + return outbuf; + } + + if (CHECK_AND_SKIP_PREFIX(arg, "keyfile:")) { + if (strlen(arg) == 0) { + BIO_printf(bio_err, "Empty key file name\n"); + return NULL; + } + keybio = BIO_new_file(arg, "rb"); + if (keybio == NULL) { + BIO_printf(bio_err, "Can't open file %s\n", arg); + return NULL; + } + inbuf = app_malloc(MAX_KEY_SIZE, "MACOPT KEYFILE"); + while (total_read < MAX_KEY_SIZE) { + n = BIO_read(keybio, inbuf + total_read, MAX_KEY_SIZE - total_read); + if (n < 0) { + BIO_printf(bio_err, "Can't read file %s\n", arg); + OPENSSL_clear_free(inbuf, MAX_KEY_SIZE); + BIO_free(keybio); + return NULL; + } + if (n == 0) /* EOF */ + break; + total_read += n; + } + too_long = (total_read == MAX_KEY_SIZE && BIO_read(keybio, &dummy, 1) > 0); + BIO_free(keybio); + if (total_read == 0 || too_long) { + /* File is empty or longer than MAX_KEY_SIZE */ + BIO_printf(bio_err, (too_long) ? "File %s is too long\n" : "File %s is empty\n", arg); + OPENSSL_clear_free(inbuf, MAX_KEY_SIZE); + return NULL; + } + outbuf = app_malloc(strlen("hexkey:") + total_read * 2 + 1, "MACOPT KEYFILE"); + strcpy(outbuf, "hexkey:"); + OPENSSL_buf2hexstr_ex(outbuf + strlen("hexkey:"), total_read * 2 + 1, + NULL, (unsigned char *)inbuf, total_read, '\0'); + OPENSSL_clear_free(inbuf, MAX_KEY_SIZE); + return outbuf; + } + + if (strcmp(arg, "keystdin") == 0) { + inbuf = get_str_from_file(NULL); + if (inbuf == NULL) + return NULL; + if (strlen(inbuf) == 0) { + BIO_printf(bio_err, "Empty key\n"); + clear_free(inbuf); + return NULL; + } + outbuf = app_malloc(strlen("key:") + strlen(inbuf) + 1, "MACOPT KEYSTDIN"); + strcpy(outbuf, "key:"); + strcat(outbuf, inbuf); + clear_free(inbuf); + return outbuf; + } + + return OPENSSL_strdup(arg); +} + +/* + * Read one line from file. + * Allocates a string with the data read and returns a pointer to this + * string, the calling code must free this string. + * If filename == NULL, read from standard input. + * Returns NULL in case of any error. + */ +char *get_str_from_file(const char *filename) +{ + static BIO *bio = NULL; + int n; + char *buf = NULL; + char *tmp; + + if (filename == NULL) { + unbuffer(stdin); + bio = dup_bio_in(FORMAT_TEXT); + if (bio == NULL) { + BIO_printf(bio_err, "Can't open BIO for stdin\n"); + return NULL; + } + } else { + bio = BIO_new_file(filename, "r"); + if (bio == NULL) { + BIO_printf(bio_err, "Can't open file %s\n", filename); + return NULL; + } + } + buf = app_malloc(MAX_KEY_SIZE, "get_str_from_file"); + memset(buf, 0, MAX_KEY_SIZE); + n = BIO_gets(bio, buf, MAX_KEY_SIZE - 1); + BIO_free_all(bio); + bio = NULL; + if (n <= 0) { + BIO_printf(bio_err, "Error reading from %s\n", filename); + return NULL; + } + tmp = strchr(buf, '\n'); + if (tmp != NULL) + *tmp = 0; + return buf; +} diff --git a/apps/mac.c b/apps/mac.c index 75ae492fecb..8a0ad3786ad 100644 --- a/apps/mac.c +++ b/apps/mac.c @@ -89,6 +89,7 @@ int mac_main(int argc, char **argv) int inform = FORMAT_BINARY; char *digest = NULL, *cipher = NULL; OSSL_PARAM *params = NULL; + char *new_opt = NULL; prog = opt_init(argc, argv, mac_options); buf = app_malloc(BUFSIZE, "I/O buffer"); @@ -112,10 +113,17 @@ opthelp: outfile = opt_arg(); break; case OPT_MACOPT: + new_opt = process_additional_mac_key_arguments(opt_arg()); + if (new_opt == NULL) { + ret = 1; + goto err; + } if (opts == NULL) opts = sk_OPENSSL_STRING_new_null(); - if (opts == NULL || !sk_OPENSSL_STRING_push(opts, opt_arg())) + if (opts == NULL || !sk_OPENSSL_STRING_push(opts, new_opt)) { + clear_free(new_opt); goto opthelp; + } break; case OPT_CIPHER: OPENSSL_free(cipher); @@ -225,9 +233,7 @@ err: if (ret != 0) ERR_print_errors(bio_err); OPENSSL_clear_free(buf, BUFSIZE); - OPENSSL_free(cipher); - OPENSSL_free(digest); - sk_OPENSSL_STRING_free(opts); + sk_OPENSSL_STRING_pop_free(opts, clear_free); BIO_free(in); BIO_free(out); EVP_MAC_CTX_free(ctx); diff --git a/doc/man1/openssl-dgst.pod.in b/doc/man1/openssl-dgst.pod.in index fb15c1e2a2a..c146d192794 100644 --- a/doc/man1/openssl-dgst.pod.in +++ b/doc/man1/openssl-dgst.pod.in @@ -27,6 +27,8 @@ B B|I [B<-signature> I] [B<-sigopt> I:I] [B<-hmac> I] +[B<-hmac-env> I] +[B<-hmac-stdin>] [B<-mac> I] [B<-macopt> I:I] [B<-fips-fingerprint>] @@ -157,6 +159,33 @@ The actual signature to verify. Create a hashed MAC using "key". +Cannot be used together with -mac option. +If multiple -hmac, -hmac-env or -hmac-stdin options are specified, the last +one is applied. + +The L command should be preferred to using this command line +option. + +=item B<-hmac-env> I + +Create a hashed MAC using a key from the environment variable "var". + +Cannot be used together with -mac option. +If multiple -hmac, -hmac-env or -hmac-stdin options are specified, the last +one is applied. + +The L command should be preferred to using this command line +option. + +=item B<-hmac-stdin> + +Create a hashed MAC using a key obtained from the standard input. Only the +first line from stdin is read, and the \0 character also terminates the key. + +Cannot be used together with -mac option. +If multiple -hmac, -hmac-env or -hmac-stdin options are specified, the last +one is applied. + The L command should be preferred to using this command line option. @@ -168,6 +197,8 @@ which are not based on hash, for instance B algorithm, supported by the B engine. MAC keys and other options should be set via B<-macopt> parameter. +Cannot be used together with -hmac, -hmac-env and -hmac-stdin. + The L command should be preferred to using this command line option. @@ -190,8 +221,37 @@ Specifies MAC key in hexadecimal form (two hex digits per byte). Key length must conform to any restrictions of the MAC algorithm for example exactly 32 chars for gost-mac. +=item BI + +Read the MAC key as an alphanumeric string from the environment variable +(use if the key contains printable characters only). +The the key length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + +=item BI + +Read the MAC key in hexadecimal form (two hex digits per byte) +from the environment variable. +The key length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + +=item BI + +Read the MAC key from the specified file. The key is read as binary data. +The key length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + +=item B + +Read the MAC key from the standard input. Only the first line from stdin is +read, and the \0 character also terminates the key. +The key length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + =back +If multiple MAC key options are specified, the last one is applied. + The L command should be preferred to using this command line option. @@ -283,6 +343,8 @@ The FIPS-related options were removed in OpenSSL 1.1.0. The B<-engine> and B<-engine_impl> options were deprecated in OpenSSL 3.0. +The B<-hmac-env> and B<-hmac-stdin> options ware added in OpenSSL 3.6. + =head1 COPYRIGHT Copyright 2000-2025 The OpenSSL Project Authors. All Rights Reserved. diff --git a/doc/man1/openssl-mac.pod.in b/doc/man1/openssl-mac.pod.in index 5d6008f002b..25d5327dc56 100644 --- a/doc/man1/openssl-mac.pod.in +++ b/doc/man1/openssl-mac.pod.in @@ -109,6 +109,41 @@ This option is identical to the B<-cipher> option. =back +Some additional options can be used only as command-line parameters. +They are not passed to the MAC algorithm, but translated into +key and hexkey parameters instead: + +=over 4 + +=item BI + +Read the MAC key as an alphanumeric string from the environment variable +(use if the key contains printable characters only). +The the key length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + +=item BI + +Read the MAC key in hexadecimal form (two hex digits per byte) +from the environment variable. +The key length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + +=item BI + +Read the MAC key from the specified file. The key is read as binary data. +The key length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + +=item B + +Read the MAC key from the standard input. Only the first line from stdin +is read, and the \0 character also terminates the key. +The key length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + +=back + {- $OpenSSL::safe::opt_provider_item -} =item I diff --git a/test/recipes/20-test_dgst.t b/test/recipes/20-test_dgst.t index 248232b7e43..cf111a63e8e 100644 --- a/test/recipes/20-test_dgst.t +++ b/test/recipes/20-test_dgst.t @@ -12,12 +12,12 @@ use warnings; use File::Spec; use File::Basename; -use OpenSSL::Test qw/:DEFAULT with srctop_file bldtop_dir/; +use OpenSSL::Test qw/:DEFAULT with srctop_file data_file bldtop_dir/; use OpenSSL::Test::Utils; setup("test_dgst"); -plan tests => 17; +plan tests => 24; sub tsignverify { my $testtext = shift; @@ -212,7 +212,51 @@ subtest "HMAC generation with `dgst` CLI, default digest" => sub { "HMAC: Check second HMAC value is consistent with the first ($hmacdata[1]) vs ($expected)"); }; -subtest "HMAC generation with `dgst` CLI, key via option" => sub { +subtest "HMAC generation with `dgst` CLI, key via environment" => sub { + plan tests => 2; + + my $testdata = srctop_file('test', 'data.bin'); + #HMAC the data twice to check consistency + local $ENV{MYKEY} = 123456; + my @hmacdata = run(app(['openssl', 'dgst', '-sha256', '-hmac-env', 'MYKEY', + $testdata, $testdata]), capture => 1); + chomp(@hmacdata); + my $expected = qr/HMAC-SHA2-256\(\Q$testdata\E\)= 6f12484129c4a761747f13d8234a1ff0e074adb34e9e9bf3a155c391b97b9a7c/; + ok($hmacdata[0] =~ $expected, "HMAC: Check HMAC value is as expected ($hmacdata[0]) vs ($expected)"); + ok($hmacdata[1] =~ $expected, + "HMAC: Check second HMAC value is consistent with the first ($hmacdata[1]) vs ($expected)"); +}; + +subtest "HMAC generation with `dgst` CLI, key via stdin" => sub { + plan tests => 2; + + my $testdata = srctop_file('test', 'data.bin'); + #HMAC the data twice to check consistency + my @hmacdata = run(app(['openssl', 'dgst', '-sha256', '-hmac-stdin', + $testdata, $testdata], stdin => data_file("keyfile.txt")), capture => 1); + chomp(@hmacdata); + my $expected = qr/HMAC-SHA2-256\(\Q$testdata\E\)= 6f12484129c4a761747f13d8234a1ff0e074adb34e9e9bf3a155c391b97b9a7c/; + ok($hmacdata[0] =~ $expected, "HMAC: Check HMAC value is as expected ($hmacdata[0]) vs ($expected)"); + ok($hmacdata[1] =~ $expected, + "HMAC: Check second HMAC value is consistent with the first ($hmacdata[1]) vs ($expected)"); +}; + +subtest "HMAC generation with `dgst` CLI, key via option key" => sub { + plan tests => 2; + + my $testdata = srctop_file('test', 'data.bin'); + #HMAC the data twice to check consistency + my @hmacdata = run(app(['openssl', 'dgst', '-sha256', '-mac', 'HMAC', + '-macopt', 'key:123456', + $testdata, $testdata]), capture => 1); + chomp(@hmacdata); + my $expected = qr/HMAC-SHA2-256\(\Q$testdata\E\)= 6f12484129c4a761747f13d8234a1ff0e074adb34e9e9bf3a155c391b97b9a7c/; + ok($hmacdata[0] =~ $expected, "HMAC: Check HMAC value is as expected ($hmacdata[0]) vs ($expected)"); + ok($hmacdata[1] =~ $expected, + "HMAC: Check second HMAC value is consistent with the first ($hmacdata[1]) vs ($expected)"); +}; + +subtest "HMAC generation with `dgst` CLI, key via option hexkey" => sub { plan tests => 2; my $testdata = srctop_file('test', 'data.bin'); @@ -227,6 +271,68 @@ subtest "HMAC generation with `dgst` CLI, key via option" => sub { "HMAC: Check second HMAC value is consistent with the first ($hmacdata[1]) vs ($expected)"); }; +subtest "HMAC generation with `dgst` CLI, key via option keyenv" => sub { + plan tests => 2; + + my $testdata = srctop_file('test', 'data.bin'); + #HMAC the data twice to check consistency + local $ENV{MYKEY} = '123456'; + my @hmacdata = run(app(['openssl', 'dgst', '-sha256', '-mac', 'HMAC', + '-macopt', 'keyenv:MYKEY', + $testdata, $testdata]), capture => 1); + chomp(@hmacdata); + my $expected = qr/HMAC-SHA2-256\(\Q$testdata\E\)= 6f12484129c4a761747f13d8234a1ff0e074adb34e9e9bf3a155c391b97b9a7c/; + ok($hmacdata[0] =~ $expected, "HMAC: Check HMAC value is as expected ($hmacdata[0]) vs ($expected)"); + ok($hmacdata[1] =~ $expected, + "HMAC: Check second HMAC value is consistent with the first ($hmacdata[1]) vs ($expected)"); +}; + +subtest "HMAC generation with `dgst` CLI, key via option keyenvhex" => sub { + plan tests => 2; + + my $testdata = srctop_file('test', 'data.bin'); + #HMAC the data twice to check consistency + local $ENV{MYKEY} = 'FFFF'; + my @hmacdata = run(app(['openssl', 'dgst', '-sha256', '-mac', 'HMAC', + '-macopt', 'keyenvhex:MYKEY', + $testdata, $testdata]), capture => 1); + chomp(@hmacdata); + my $expected = qr/HMAC-SHA2-256\(\Q$testdata\E\)= 7c02d4a17d2560a5bb6763edbf33f3a34f415398f8f2e07f04b83ffd7c087dae/; + ok($hmacdata[0] =~ $expected, "HMAC: Check HMAC value is as expected ($hmacdata[0]) vs ($expected)"); + ok($hmacdata[1] =~ $expected, + "HMAC: Check second HMAC value is consistent with the first ($hmacdata[1]) vs ($expected)"); +}; + +subtest "HMAC generation with `dgst` CLI, key via option keyfile" => sub { + plan tests => 2; + + my $testdata = srctop_file('test', 'data.bin'); + #HMAC the data twice to check consistency + my @hmacdata = run(app(['openssl', 'dgst', '-sha256', '-mac', 'HMAC', + '-macopt', 'keyfile:' . data_file("keyfile.bin"), + $testdata, $testdata]), capture => 1); + chomp(@hmacdata); + my $expected = qr/HMAC-SHA2-256\(\Q$testdata\E\)= 7c02d4a17d2560a5bb6763edbf33f3a34f415398f8f2e07f04b83ffd7c087dae/; + ok($hmacdata[0] =~ $expected, "HMAC: Check HMAC value is as expected ($hmacdata[0]) vs ($expected)"); + ok($hmacdata[1] =~ $expected, + "HMAC: Check second HMAC value is consistent with the first ($hmacdata[1]) vs ($expected)"); +}; + +subtest "HMAC generation with `dgst` CLI, key via option keystdin" => sub { + plan tests => 2; + + my $testdata = srctop_file('test', 'data.bin'); + #HMAC the data twice to check consistency + my @hmacdata = run(app(['openssl', 'dgst', '-sha256', '-mac', 'HMAC', + '-macopt', 'keystdin', + $testdata, $testdata], stdin => data_file("keyfile.txt")), capture => 1); + chomp(@hmacdata); + my $expected = qr/HMAC-SHA2-256\(\Q$testdata\E\)= 6f12484129c4a761747f13d8234a1ff0e074adb34e9e9bf3a155c391b97b9a7c/; + ok($hmacdata[0] =~ $expected, "HMAC: Check HMAC value is as expected ($hmacdata[0]) vs ($expected)"); + ok($hmacdata[1] =~ $expected, + "HMAC: Check second HMAC value is consistent with the first ($hmacdata[1]) vs ($expected)"); +}; + subtest "Custom length XOF digest generation with `dgst` CLI" => sub { plan tests => 2; diff --git a/test/recipes/20-test_dgst_data/keyfile.bin b/test/recipes/20-test_dgst_data/keyfile.bin new file mode 100644 index 00000000000..f96c401f328 --- /dev/null +++ b/test/recipes/20-test_dgst_data/keyfile.bin @@ -0,0 +1 @@ +ÿÿ \ No newline at end of file diff --git a/test/recipes/20-test_dgst_data/keyfile.txt b/test/recipes/20-test_dgst_data/keyfile.txt new file mode 100644 index 00000000000..4632e068d58 --- /dev/null +++ b/test/recipes/20-test_dgst_data/keyfile.txt @@ -0,0 +1 @@ +123456 \ No newline at end of file diff --git a/test/recipes/20-test_mac.t b/test/recipes/20-test_mac.t index cc25e774535..35a4904188c 100644 --- a/test/recipes/20-test_mac.t +++ b/test/recipes/20-test_mac.t @@ -10,7 +10,7 @@ use strict; use warnings; -use OpenSSL::Test; +use OpenSSL::Test qw(:DEFAULT data_file); use OpenSSL::Test::Utils; use Storable qw(dclone); @@ -52,6 +52,29 @@ my @mac_tests = ( input => '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7', expected => 'D5BE731C954ED7732846BB59DBE3A8E30F83E77A4BFF4459F2F1C2B4ECEBB8CE67BA01C62E8AB8578D2D499BD1BB276768781190020A306A97DE281DCC30305D', desc => 'KMAC256 with xof len of 64' }, + { cmd => [qw{openssl mac -digest SHA256 -macopt keyenv:MACKEY}], + env => {'MACKEY' => 'ASCII_key_for_HMAC_tests' }, + type => 'HMAC', + input => unpack("H*", "Sample message for additional key options"), + expected => '70B1E97C0EDDBD4CB4866E28FEBA45343BD35FD88437F17880B7ADAC058B161B', + desc => 'HMAC with keyenv' }, + { cmd => [qw{openssl mac -digest SHA256 -macopt keyenvhex:MACKEY}], + env => {'MACKEY' => '41534349495F6B65795F666F725F484D41435F7465737473' }, + type => 'HMAC', + input => unpack("H*", "Sample message for additional key options"), + expected => '70B1E97C0EDDBD4CB4866E28FEBA45343BD35FD88437F17880B7ADAC058B161B', + desc => 'HMAC with keyenvhex' }, + { cmd => [qw{openssl mac -digest SHA256}, '-macopt', 'keyfile:' . data_file("keyfile.dat")], + type => 'HMAC', + input => unpack("H*", "Sample message for additional key options"), + expected => '70B1E97C0EDDBD4CB4866E28FEBA45343BD35FD88437F17880B7ADAC058B161B', + desc => 'HMAC with keyfile' }, + { cmd => [qw{openssl mac -digest SHA256 -macopt keystdin}], + keyinput => data_file("keyfile.dat"), + type => 'HMAC', + input => unpack("H*", "Sample message for additional key options"), + expected => '70B1E97C0EDDBD4CB4866E28FEBA45343BD35FD88437F17880B7ADAC058B161B', + desc => 'HMAC with keystdin' }, ); my @siphash_tests = ( @@ -121,11 +144,29 @@ my $test_count = 0; foreach (@mac_tests) { $test_count++; - ok(compareline($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}, $_->{err}), $_->{desc}); + if ($_->{env}) { + ok( do { + local @ENV{keys %{$_->{env}}} = values %{$_->{env}}; + compareline($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}, $_->{err}) + }, $_->{desc}); + } elsif ($_->{keyinput}) { + ok(compareline($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}, $_->{err}, $_->{keyinput}), $_->{desc}); + } else { + ok(compareline($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}, $_->{err}), $_->{desc}); + } } foreach (@mac_tests) { $test_count++; - ok(comparefile($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}), $_->{desc}); + if ($_->{env}) { + ok( do { + local @ENV{keys %{$_->{env}}} = values %{$_->{env}}; + comparefile($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}) + }, $_->{desc}); + } elsif ($_->{keyinput}) { + ok(comparefile($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}, $_->{keyinput}), $_->{desc}); + } else { + ok(comparefile($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}), $_->{desc}); + } } foreach (@mac_fail_tests) { @@ -137,7 +178,7 @@ foreach (@mac_fail_tests) { # then compare the stdout output matches the expected value. sub compareline { my $tmpfile = "input-$test_count.bin"; - my ($cmdarray_orig, $type, $input, $expect, $err) = @_; + my ($cmdarray_orig, $type, $input, $expect, $err, $keyinput) = @_; my $cmdarray = dclone $cmdarray_orig; if (defined($expect)) { $expect = uc $expect; @@ -153,7 +194,7 @@ sub compareline { my @other = ('-in', $tmpfile, $type); push @$cmdarray, @other; - my @lines = run(app($cmdarray), capture => 1); + my @lines = run(app($cmdarray, stdin => $keyinput), capture => 1); # Not unlinking $tmpfile if (defined($expect)) { @@ -189,7 +230,7 @@ sub compareline { sub comparefile { my $tmpfile = "input-$test_count.bin"; my $outfile = "output-$test_count.bin"; - my ($cmdarray, $type, $input, $expect) = @_; + my ($cmdarray, $type, $input, $expect, $keyinput) = @_; $expect = uc $expect; # Open a temporary input file and write $input to it @@ -202,7 +243,7 @@ sub comparefile { my @other = ("-binary", "-in", $tmpfile, "-out", $outfile, $type); push @$cmdarray, @other; - run(app($cmdarray)); + run(app($cmdarray, stdin => $keyinput)); # Not unlinking $tmpfile open(my $out, '<', $outfile) or die "Could not open file"; diff --git a/test/recipes/20-test_mac_data/keyfile.dat b/test/recipes/20-test_mac_data/keyfile.dat new file mode 100644 index 00000000000..3c4ca002d34 --- /dev/null +++ b/test/recipes/20-test_mac_data/keyfile.dat @@ -0,0 +1 @@ +ASCII_key_for_HMAC_tests \ No newline at end of file -- 2.47.3