From: Shane Lontis Date: Tue, 20 Nov 2018 00:45:44 +0000 (+1000) Subject: openssl app for macs that uses the new EVP_MAC interface (the code inside dgst uses... X-Git-Tag: openssl-3.0.0-alpha1~2404 X-Git-Url: https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff_plain;h=4d768e966ff9a9777b2b4a79f85a737ab2ded666 openssl app for macs that uses the new EVP_MAC interface (the code inside dgst uses EVP_PKEY) Reviewed-by: Paul Yang Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/7661) --- diff --git a/apps/build.info b/apps/build.info index 9b77c469f5..ad14038a5d 100644 --- a/apps/build.info +++ b/apps/build.info @@ -2,10 +2,10 @@ qw(openssl.c asn1pars.c ca.c ciphers.c cms.c crl.c crl2p7.c dgst.c dhparam.c dsa.c dsaparam.c ec.c ecparam.c enc.c engine.c errstr.c gendsa.c - genpkey.c genrsa.c nseq.c ocsp.c passwd.c pkcs12.c pkcs7.c pkcs8.c - pkey.c pkeyparam.c pkeyutl.c prime.c rand.c req.c rsa.c rsautl.c - s_client.c s_server.c s_time.c sess_id.c smime.c speed.c spkac.c - srp.c ts.c verify.c version.c x509.c rehash.c storeutl.c); + genpkey.c genrsa.c mac.c nseq.c ocsp.c passwd.c pkcs12.c pkcs7.c + pkcs8.c pkey.c pkeyparam.c pkeyutl.c prime.c rand.c req.c rsa.c + rsautl.c s_client.c s_server.c s_time.c sess_id.c smime.c speed.c + spkac.c srp.c ts.c verify.c version.c x509.c rehash.c storeutl.c); our @apps_lib_src = ( qw(apps.c apps_ui.c opt.c fmt.c s_cb.c s_socket.c app_rand.c bf_prefix.c), diff --git a/apps/mac.c b/apps/mac.c new file mode 100644 index 0000000000..a02779b29f --- /dev/null +++ b/apps/mac.c @@ -0,0 +1,200 @@ +/* + * Copyright 2018 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the OpenSSL license (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include + +#include "apps.h" +#include "progs.h" +#include +#include +#include + +#undef BUFSIZE +#define BUFSIZE 1024*8 + +typedef enum OPTION_choice { + OPT_ERR = -1, OPT_EOF = 0, OPT_HELP, + OPT_MACOPT, OPT_BIN, OPT_IN, OPT_OUT +} OPTION_CHOICE; + +const OPTIONS mac_options[] = { + {OPT_HELP_STR, 1, '-', "Usage: %s [options] mac_name\n"}, + {OPT_HELP_STR, 1, '-', "mac_name\t\t MAC algorithm (See list " + "-mac-algorithms)"}, + {"help", OPT_HELP, '-', "Display this summary"}, + {"macopt", OPT_MACOPT, 's', "MAC algorithm control parameters in n:v form. " + "See 'Supported Controls' in the EVP_MAC_ docs"}, + {"in", OPT_IN, '<', "Input file to MAC (default is stdin)"}, + {"out", OPT_OUT, '>', "Output to filename rather than stdout"}, + {"binary", OPT_BIN, '-', "Output in binary format (Default is hexadecimal " + "output)"}, + {NULL} +}; + +static int mac_ctrl_string(EVP_MAC_CTX *ctx, const char *value) +{ + int rv; + char *stmp, *vtmp = NULL; + + stmp = OPENSSL_strdup(value); + if (stmp == NULL) + return -1; + vtmp = strchr(stmp, ':'); + if (vtmp != NULL) { + *vtmp = 0; + vtmp++; + } + rv = EVP_MAC_ctrl_str(ctx, stmp, vtmp); + OPENSSL_free(stmp); + return rv; +} + +int mac_main(int argc, char **argv) +{ + int ret = 1; + char *prog; + const EVP_MAC *mac = NULL; + OPTION_CHOICE o; + EVP_MAC_CTX *ctx = NULL; + STACK_OF(OPENSSL_STRING) *opts = NULL; + unsigned char *buf = NULL; + size_t len; + int i; + BIO *in = NULL, *out = NULL; + const char *outfile = NULL; + const char *infile = NULL; + int out_bin = 0; + int inform = FORMAT_BINARY; + + prog = opt_init(argc, argv, mac_options); + buf = app_malloc(BUFSIZE, "I/O buffer"); + while ((o = opt_next()) != OPT_EOF) { + switch (o) { + case OPT_EOF: + case OPT_ERR: +opthelp: + BIO_printf(bio_err, "%s: Use -help for summary.\n", prog); + goto err; + case OPT_HELP: + opt_help(mac_options); + ret = 0; + goto err; + case OPT_BIN: + out_bin = 1; + break; + case OPT_IN: + infile = opt_arg(); + break; + case OPT_OUT: + outfile = opt_arg(); + break; + case OPT_MACOPT: + if (opts == NULL) + opts = sk_OPENSSL_STRING_new_null(); + if (opts == NULL || !sk_OPENSSL_STRING_push(opts, opt_arg())) + goto opthelp; + break; + } + } + argc = opt_num_rest(); + argv = opt_rest(); + + if (argc != 1) { + BIO_printf(bio_err, "Invalid number of extra arguments\n"); + goto opthelp; + } + + mac = EVP_get_macbyname(argv[0]); + if (mac == NULL) { + BIO_printf(bio_err, "Invalid MAC name %s\n", argv[0]); + goto opthelp; + } + + ctx = EVP_MAC_CTX_new(mac); + if (ctx == NULL) + goto err; + + if (opts != NULL) { + for (i = 0; i < sk_OPENSSL_STRING_num(opts); i++) { + char *opt = sk_OPENSSL_STRING_value(opts, i); + if (mac_ctrl_string(ctx, opt) <= 0) { + BIO_printf(bio_err, "MAC parameter error '%s'\n", opt); + ERR_print_errors(bio_err); + goto err; + } + } + } + + /* Use text mode for stdin */ + if (infile == NULL || strcmp(infile, "-") == 0) + inform = FORMAT_TEXT; + in = bio_open_default(infile, 'r', inform); + if (in == NULL) + goto err; + + out = bio_open_default(outfile, 'w', out_bin ? FORMAT_BINARY : FORMAT_TEXT); + if (out == NULL) + goto err; + + if (!EVP_MAC_init(ctx)) { + BIO_printf(bio_err, "EVP_MAC_Init failed\n"); + goto err; + } + + + for (;;) { + i = BIO_read(in, (char *)buf, BUFSIZE); + if (i < 0) { + BIO_printf(bio_err, "Read Error in '%s'\n", infile); + goto err; + } + if (i == 0) + break; + if (!EVP_MAC_update(ctx, buf, i)) { + BIO_printf(bio_err, "EVP_MAC_update failed\n"); + goto err; + } + } + + if (!EVP_MAC_final(ctx, NULL, &len)) { + BIO_printf(bio_err, "EVP_MAC_final failed\n"); + goto err; + } + if (len > BUFSIZE) { + BIO_printf(bio_err, "output len is too large\n"); + goto err; + } + + if (!EVP_MAC_final(ctx, buf, &len)) { + BIO_printf(bio_err, "EVP_MAC_final failed\n"); + goto err; + } + + if (out_bin) { + BIO_write(out, buf, len); + } else { + if (outfile == NULL) + BIO_printf(out,"\n"); + for (i = 0; i < (int)len; ++i) + BIO_printf(out, "%02X", buf[i]); + if (outfile == NULL) + BIO_printf(out,"\n"); + } + + ret = 0; +err: + if (ret != 0) + ERR_print_errors(bio_err); + OPENSSL_clear_free(buf, BUFSIZE); + sk_OPENSSL_STRING_free(opts); + BIO_free(in); + BIO_free(out); + EVP_MAC_CTX_free(ctx); + return ret; +} diff --git a/doc/man1/mac.pod b/doc/man1/mac.pod new file mode 100644 index 0000000000..5d1e796657 --- /dev/null +++ b/doc/man1/mac.pod @@ -0,0 +1,163 @@ +=pod + +=head1 NAME + +openssl-mac, +mac - perform Message Authentication Code operations + +=head1 SYNOPSIS + +B +[B<-help>] +[B<-macopt>] +[B<-in filename>] +[B<-out filename>] +[B<-binary>] +B + +B I [B<...>] B + +=head1 DESCRIPTION + +The message authentication code functions output the MAC of a supplied input +file. + +=head1 OPTIONS + +=over 4 + +=item B<-help> + +Print a usage message. + +=item B<-in filename> + +Input filename to calculate a MAC for, or standard input by default. +Standard input is used if the filename is '-'. +Files are expected to be in binary format, standard input uses hexadecimal text +format. + +=item B<-out filename> + +Filename to output to, or standard output by default. + +=item B<-binary> + +Output the MAC in binary form. Uses hexadecimal text format if not specified. + +=item B<-macopt nm:v> + +Passes options to the MAC algorithm. +A comprehensive list of controls can be found in the EVP_MAC implementation +documentation. +Common control strings used by EVP_MAC_ctrl_str() are: + +=over 4 + +=item B + +Specifies the MAC key as an alphanumeric string (use if the key contains +printable characters only). +The string length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + +=item B + +Specifies the MAC key in hexadecimal form (two hex digits per byte). +The key length must conform to any restrictions of the MAC algorithm. +A key must be specified for every MAC algorithm. + +=item B + +Used by HMAC as an alphanumeric string (use if the key contains printable +characters only). +The string length must conform to any restrictions of the MAC algorithm. +To see the list of supported digests, use the command I. + +=item B + +Used by CMAC and GMAC to specifiy the cipher algorithm. +For CMAC it must be one of AES-128-CBC, AES-192-CBC, AES-256-CBC or +DES-EDE3-CBC. +For GMAC it should be a GCM mode cipher e.g. AES-128-GCM. + +=item B + +Used by GMAC to specify an IV as an alphanumeric string (use if the IV contains +printable characters only). + +=item B + +Used by GMAC to specify an IV in hexadecimal form (two hex digits per byte). + +=item B + +Used by KMAC128 or KMAC256 to specify an output length. +The default sizes are 32 or 64 bytes respectively. + +=item B + +Used by KMAC128 or KMAC256 to specify a customization string. +The default is the empty string "". + +=back + +=item B + +Specifies the name of a supported MAC algorithm which will be used. +To see the list of supported MAC's use the command I. + +=back + + +=head1 EXAMPLES + +To create a hex-encoded HMAC-SHA1 MAC of a file and write to stdout: \ + openssl mac -macopt digest:SHA1 \ + -macopt hexkey:000102030405060708090A0B0C0D0E0F10111213 \ + -in msg.bin HMAC + +To create a SipHash MAC from a file with a binary file output: \ + openssl mac -macopt hexkey:000102030405060708090A0B0C0D0E0F \ + -in msg.bin -out out.bin -binary SipHash + +To create a hex-encoded CMAC-AES-128-CBC MAC from a file:\ + openssl mac -macopt cipher:AES-128-CBC \ + -macopt hexkey:77A77FAF290C1FA30C683DF16BA7A77B \ + -in msg.bin CMAC + +To create a hex-encoded KMAC128 MAC from a file with a Customisation String +'Tag' and output length of 16: \ + openssl mac -macopt custom:Tag -macopt hexkey:40414243444546 \ + -macopt outlen:16 -in msg.bin KMAC128 + +To create a hex-encoded GMAC-AES-128-GCM with a IV from a file: \ + openssl mac -macopt cipher:AES-128-GCM -macopt hexiv:E0E00F19FED7BA0136A797F3 \ + -macopt hexkey:77A77FAF290C1FA30C683DF16BA7A77B -in msg.bin GMAC + +=head1 NOTES + +The MAC mechanisms that are available will depend on the options +used when building OpenSSL. +The B command can be used to list them. + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L + +=head1 COPYRIGHT + +Copyright 2018 The OpenSSL Project Authors. All Rights Reserved. + +Licensed under the OpenSSL license (the "License"). You may not use +this file except in compliance with the License. You can obtain a copy +in the file LICENSE in the source distribution or at +L. + +=cut diff --git a/doc/man1/openssl.pod b/doc/man1/openssl.pod index ca4f78c238..5f6f8d3bbf 100644 --- a/doc/man1/openssl.pod +++ b/doc/man1/openssl.pod @@ -11,7 +11,7 @@ I [ I ] [ I ] -B B [ B | B | B | B | B | B] +B B [ B | B | B | B | B | B | B] B BI [ I ] @@ -28,7 +28,7 @@ It can be used for o Creation and management of private keys, public keys and parameters o Public key cryptographic operations o Creation of X.509 certificates, CSRs and CRLs - o Calculation of Message Digests + o Calculation of Message Digests and Message Authentication Codes o Encryption and Decryption with Ciphers o SSL/TLS Client and Server Tests o Handling of S/MIME signed or encrypted mail @@ -57,8 +57,9 @@ and B output a list (one entry per line) of the names of all standard commands, message digest commands, or cipher commands, respectively, that are available in the present B utility. -The list parameters B and -B list all cipher and message digest names, one entry per line. Aliases are listed as: +The list parameters B, B, +and B list all cipher, message digest, and message +authentication code names, one entry per line. Aliases are listed as: from => to @@ -106,7 +107,8 @@ CRL to PKCS#7 Conversion. =item B -Message Digest Calculation. +Message Digest calculation. MAC calculations are superseded by +L. =item B @@ -165,6 +167,10 @@ Generation of Private Key or Parameters. Generation of RSA Private Key. Superseded by L. +=item B + +Message Authentication Code Calculation. + =item B Create or examine a Netscape certificate sequence. @@ -606,7 +612,7 @@ L, L, L, L, L, L, L, L, L, L, L, L, L, -L, L, L, +L, L, L, L, L, L, L, L, L, L, L, L, diff --git a/test/recipes/20-test_mac.t b/test/recipes/20-test_mac.t new file mode 100644 index 0000000000..963c3d79c5 --- /dev/null +++ b/test/recipes/20-test_mac.t @@ -0,0 +1,176 @@ +#! /usr/bin/env perl +# Copyright 2018 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the OpenSSL license (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + + +use strict; +use warnings; + +use OpenSSL::Test; +use OpenSSL::Test::Utils; +use Storable qw(dclone); + +setup("test_mac"); + +my @mac_tests = ( + { cmd => [qw{openssl mac -macopt hexkey:000102030405060708090A0B0C0D0E0F}], + type => 'SipHash', + input => '00', + expected => 'da87c1d86b99af44347659119b22fc45', + desc => 'SipHash No input' }, + { cmd => [qw{openssl mac -macopt digest:SHA1 -macopt hexkey:000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F}], + type => 'HMAC', + input => unpack("H*", "Sample message for keylen=blocklen"), + expected => '5FD596EE78D5553C8FF4E72D266DFD192366DA29', + desc => 'HMAC SHA1' }, + { cmd => [qw{openssl mac -macopt cipher:AES-256-CBC -macopt hexkey:0B122AC8F34ED1FE082A3625D157561454167AC145A10BBF77C6A70596D574F1}], + type => 'CMAC', + input => '498B53FDEC87EDCBF07097DCCDE93A084BAD7501A224E388DF349CE18959FE8485F8AD1537F0D896EA73BEDC7214713F', + expected => 'F62C46329B41085625669BAF51DEA66A', + desc => 'CMAC AES-256-CBC' }, + { cmd => [qw{openssl mac -macopt hexkey:02000000000000000000000000000000ffffffffffffffffffffffffffffffff}], + type => 'Poly1305', + input => '02000000000000000000000000000000', + expected => '03000000000000000000000000000000', + desc => 'Poly1305 (wrap 2^128)' }, + { cmd => [qw{openssl mac -macopt cipher:AES-256-GCM -macopt hexkey:4C973DBC7364621674F8B5B89E5C15511FCED9216490FB1C1A2CAA0FFE0407E5 -macopt hexiv:7AE8E2CA4EC500012E58495C}], + type => 'GMAC', + input => '68F2E77696CE7AE8E2CA4EC588E541002E58495C08000F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D0007', + expected => '00BDA1B7E87608BCBF470F12157F4C07', + desc => 'GMAC' }, + { cmd => [qw{openssl mac -macopt hexkey:404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F -macopt xof:0}], + type => 'KMAC128', + input => '00010203', + expected => 'E5780B0D3EA6F7D3A429C5706AA43A00FADBD7D49628839E3187243F456EE14E', + desc => 'KMAC128' }, + { cmd => [qw{openssl mac -macopt hexkey:404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F -macopt }, 'custom:My Tagged Application'], + type => 'KMAC256', + input => '00010203', + expected => '20C570C31346F703C9AC36C61C03CB64C3970D0CFC787E9B79599D273A68D2F7F69D4CC3DE9D104A351689F27CF6F5951F0103F33F4F24871024D9C27773A8DD', + desc => 'KMAC256' }, + { cmd => [qw{openssl mac -macopt hexkey:404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F -macopt xof:1 -macopt}, 'custom:My Tagged Application'], + type => 'KMAC256', + input => '000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7', + expected => 'D5BE731C954ED7732846BB59DBE3A8E30F83E77A4BFF4459F2F1C2B4ECEBB8CE67BA01C62E8AB8578D2D499BD1BB276768781190020A306A97DE281DCC30305D', + desc => 'KMAC256 with xof len of 64' }, +); + +my @mac_fail_tests = ( + { cmd => [qw{openssl mac}], + type => 'SipHash', + input => '00', + err => '', + desc => 'SipHash Fail no key' }, + { cmd => [qw{openssl mac}], + type => 'KMAC128', + input => '00', + err => 'EVP_MAC_Init', + desc => 'KMAC128 Fail no key' }, +); + +plan tests => (scalar @mac_tests * 2) + scalar @mac_fail_tests; + +foreach (@mac_tests) { + ok(compareline($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}, $_->{err}), $_->{desc}); +} +foreach (@mac_tests) { + ok(comparefile($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}), $_->{desc}); +} + +foreach (@mac_fail_tests) { + ok(compareline($_->{cmd}, $_->{type}, $_->{input}, $_->{expected}, $_->{err}), $_->{desc}); +} + +# Create a temp input file and save the input data into it, and +# then compare the stdout output matches the expected value. +sub compareline { + my $tmpfile = 'tmp.bin'; + my ($cmdarray_orig, $type, $input, $expect, $err) = @_; + my $cmdarray = dclone $cmdarray_orig; + if (defined($expect)) { + $expect = uc $expect; + } + # Open a temporary input file and write $input to it + open(my $in, '>', $tmpfile) or die "Could not open file"; + binmode($in); + my $bin = pack("H*", $input); + print $in $bin; + close $in; + + # The last cmd parameter is the temporary input file we just created. + my @other = ('-in', $tmpfile, $type); + push @$cmdarray, @other; + + my @lines = run(app($cmdarray), capture => 1); + unlink $tmpfile; + + if (defined($expect)) { + if ($lines[1] =~ m|^\Q${expect}\E\R$|) { + return 1; + } else { + print "Got: $lines[1]"; + print "Exp: $expect\n"; + return 0; + } + } + if (defined($err)) { + if (defined($lines[0])) { + $lines[0] =~ s/\s+$//; + if ($lines[0] eq $err) { + return 1; + } else { + print "Got: $lines[0]"; + print "Exp: $err\n"; + return 0; + } + } else { + # expected an error + return 1; + } + } + return 0; +} + +# Create a temp input file and save the input data into it, and +# use the '-bin -out ' commandline options to save results out to a file. +# Read this file back in and check its output matches the expected value. +sub comparefile { + my $tmpfile = 'tmp.bin'; + my $outfile = 'out.bin'; + my ($cmdarray, $type, $input, $expect) = @_; + $expect = uc $expect; + + # Open a temporary input file and write $input to it + open(my $in, '>', $tmpfile) or die "Could not open file"; + binmode($in); + my $bin = pack("H*", $input); + print $in $bin; + close $in; + + my @other = ("-binary", "-in", $tmpfile, "-out", $outfile, $type); + push @$cmdarray, @other; + + run(app($cmdarray)); + unlink $tmpfile; + open(my $out, '<', $outfile) or die "Could not open file"; + binmode($out); + my $buffer; + my $BUFSIZE = 1024; + read($out, $buffer, $BUFSIZE) or die "unable to read"; + + my $line = uc unpack("H*", $buffer); + close($out); + unlink $outfile; + + if ($line eq $expect) { + return 1; + } else { + print "Got: $line\n"; + print "Exp: $expect\n"; + return 0; + } +}