]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
dgst and mac apps: Added new ways for obtaining a MAC key
authorIgor Ustinov <igus68@gmail.com>
Sat, 2 Aug 2025 16:53:13 +0000 (19:53 +0300)
committerTomas Mraz <tomas@openssl.org>
Tue, 9 Sep 2025 07:58:10 +0000 (09:58 +0200)
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 <beldmit@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28160)

apps/dgst.c
apps/include/apps.h
apps/lib/apps.c
apps/mac.c
doc/man1/openssl-dgst.pod.in
doc/man1/openssl-mac.pod.in
test/recipes/20-test_dgst.t
test/recipes/20-test_dgst_data/keyfile.bin [new file with mode: 0644]
test/recipes/20-test_dgst_data/keyfile.txt [new file with mode: 0644]
test/recipes/20-test_mac.t
test/recipes/20-test_mac_data/keyfile.dat [new file with mode: 0644]

index 3257acd0c3bff130ca09e22ba6d26c2def648941..07fa5fe26d2af0e8a69d8c92d1d0b9744d104f4b 100644 (file)
@@ -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);
index 607e2e1e90a871c818c13bfea0d48d8377aa95ac..58e9210236298e92515ff637c41def1a797429bb 100644 (file)
@@ -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,
index fbb3dbf3dcae98538385d1701b1edb8a303e7d48..fe9519e1ef39feb56ac3402a1f742d26d204e0bd 100644 (file)
@@ -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;
+}
index 75ae492fecb3f9bdd614a8d19e3de3c77798c711..8a0ad3786adac129809442b1b28c432ca7f86211 100644 (file)
@@ -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);
index fb15c1e2a2a9d6f7526434cc6016677380a7e7cc..c146d192794932242070ad7abd7e06855be8e25e 100644 (file)
@@ -27,6 +27,8 @@ B<openssl> B<dgst>|I<digest>
 [B<-signature> I<filename>]
 [B<-sigopt> I<nm>:I<v>]
 [B<-hmac> I<key>]
+[B<-hmac-env> I<var>]
+[B<-hmac-stdin>]
 [B<-mac> I<alg>]
 [B<-macopt> I<nm>:I<v>]
 [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<openssl-mac(1)> command should be preferred to using this command line
+option.
+
+=item B<-hmac-env> I<var>
+
+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<openssl-mac(1)> 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<openssl-mac(1)> command should be preferred to using this command line
 option.
 
@@ -168,6 +197,8 @@ which are not based on hash, for instance B<gost-mac> algorithm,
 supported by the B<gost> 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<openssl-mac(1)> 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 B<keyenv:>I<var>
+
+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 B<keyenvhex:>I<var>
+
+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 B<keyfile:>I<filename>
+
+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<keystdin>
+
+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<openssl-mac(1)> 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.
index 5d6008f002b3f8dab42c477d39f6c26979eb9407..25d5327dc5686622d40b1699c78e7f2d5735da25 100644 (file)
@@ -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 B<keyenv:>I<var>
+
+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 B<keyenvhex:>I<var>
+
+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 B<keyfile:>I<filename>
+
+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<keystdin>
+
+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<mac_name>
index 248232b7e43a61091bad4f35a9c77108848d4cf1..cf111a63e8e41ad539b80290c6f7857cded8dd34 100644 (file)
@@ -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 (file)
index 0000000..f96c401
--- /dev/null
@@ -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 (file)
index 0000000..4632e06
--- /dev/null
@@ -0,0 +1 @@
+123456
\ No newline at end of file
index cc25e774535f6ec4e6c7078c641a70d5f268b657..35a4904188cc21113f9d46baeed71e83d2583fa4 100644 (file)
@@ -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 (file)
index 0000000..3c4ca00
--- /dev/null
@@ -0,0 +1 @@
+ASCII_key_for_HMAC_tests
\ No newline at end of file