From 0d53de14d01e8e079e5b5b4e08ec6e9f063139cc Mon Sep 17 00:00:00 2001 From: Richard Levitte Date: Mon, 5 Apr 2021 08:08:10 +0200 Subject: [PATCH] Making a gost provider - Add the macs We add the macs for the provider as wrappers around the EVP_MD implementations designed for ENGINEs. This is not the most elegant, but it does the job. When an algorithm has an OID, it's included in the OSSL_ALGORITHM name as an alias. This is the way to avoid having to register the OIDs in OpenSSL proper. --- CMakeLists.txt | 1 + gost_lcl.h | 4 + gost_prov.c | 3 + gost_prov_mac.c | 361 ++++++++++++++++++++++++++++++++++++++++++++++++ test/02-mac.t | 210 ++++++++++++++++++++++++---- 5 files changed, 554 insertions(+), 25 deletions(-) create mode 100644 gost_prov_mac.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c95e5ea..740cac3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,6 +201,7 @@ set(GOST_PROV_SOURCE_FILES gost_prov.c gost_prov_cipher.c gost_prov_digest.c + gost_prov_mac.c ) set(TEST_ENVIRONMENT_COMMON diff --git a/gost_lcl.h b/gost_lcl.h index 0e544ee..e785404 100644 --- a/gost_lcl.h +++ b/gost_lcl.h @@ -53,6 +53,10 @@ void gost_param_free(void); /* method registration */ +/* Provider implementation data */ +extern const OSSL_ALGORITHM GOST_prov_macs[]; +void GOST_prov_deinit_mac_digests(void); + int register_ameth_gost(int nid, EVP_PKEY_ASN1_METHOD **ameth, const char *pemstr, const char *info); int register_pmeth_gost(int id, EVP_PKEY_METHOD **pmeth, int flags); diff --git a/gost_prov.c b/gost_prov.c index ba7120b..e7fdd96 100644 --- a/gost_prov.c +++ b/gost_prov.c @@ -91,6 +91,8 @@ static const OSSL_ALGORITHM *gost_operation(void *vprovctx, return GOST_prov_ciphers; case OSSL_OP_DIGEST: return GOST_prov_digests; + case OSSL_OP_MAC: + return GOST_prov_macs; } return NULL; } @@ -113,6 +115,7 @@ static void gost_teardown(void *vprovctx) { GOST_prov_deinit_ciphers(); GOST_prov_deinit_digests(); + GOST_prov_deinit_mac_digests(); provider_ctx_free(vprovctx); } diff --git a/gost_prov_mac.c b/gost_prov_mac.c new file mode 100644 index 0000000..ab04a9c --- /dev/null +++ b/gost_prov_mac.c @@ -0,0 +1,361 @@ +/********************************************************************** + * gost_prov_mac.c - Initialize all macs * + * * + * Copyright (c) 2021 Richard Levitte * + * This file is distributed under the same license as OpenSSL * + * * + * OpenSSL provider interface to GOST mac functions * + * Requires OpenSSL 3.0 for compilation * + **********************************************************************/ + +#include +#include +#include "gost_prov.h" +#include "gost_lcl.h" + +/* + * Forward declarations of all generic OSSL_DISPATCH functions, to make sure + * they are correctly defined further down. For the algorithm specific ones + * MAKE_FUNCTIONS() does it for us. + */ + +static OSSL_FUNC_mac_dupctx_fn mac_dupctx; +static OSSL_FUNC_mac_freectx_fn mac_freectx; +static OSSL_FUNC_mac_init_fn mac_init; +static OSSL_FUNC_mac_update_fn mac_update; +static OSSL_FUNC_mac_final_fn mac_final; +static OSSL_FUNC_mac_get_ctx_params_fn mac_get_ctx_params; +static OSSL_FUNC_mac_set_ctx_params_fn mac_set_ctx_params; + +struct gost_prov_mac_desc_st { + /* + * In the GOST engine, the MAC implementation bases itself heavily on + * digests with the same name. We can re-use that part. + */ + GOST_digest *digest_desc; + size_t initial_mac_size; +}; +typedef struct gost_prov_mac_desc_st GOST_DESC; + +struct gost_prov_mac_ctx_st { + /* Provider context */ + PROV_CTX *provctx; + const GOST_DESC *descriptor; + + /* Output MAC size */ + size_t mac_size; + /* XOF mode, where applicable */ + int xof_mode; + + /* + * Since existing functionality is mainly designed as EVP_MDs for + * ENGINEs, the functions in this file are accomodated and are simply + * wrappers that use a local EVP_MD and EVP_MD_CTX. + * Future development should take a more direct approach and have the + * appropriate digest functions and digest data directly in this context. + */ + + /* The EVP_MD created from |descriptor| */ + EVP_MD *digest; + /* The context for the EVP_MD functions */ + EVP_MD_CTX *dctx; +}; +typedef struct gost_prov_mac_ctx_st GOST_CTX; + +static void mac_freectx(void *vgctx) +{ + GOST_CTX *gctx = vgctx; + + /* + * We don't free gctx->digest here. + * That will be done by the provider teardown, via + * GOST_prov_deinit_digests() (defined at the bottom of this file). + */ + EVP_MD_CTX_free(gctx->dctx); + OPENSSL_free(gctx); +} + +static GOST_CTX *mac_newctx(void *provctx, const GOST_DESC *descriptor) +{ + GOST_CTX *gctx = NULL; + + if ((gctx = OPENSSL_zalloc(sizeof(*gctx))) != NULL) { + gctx->provctx = provctx; + gctx->descriptor = descriptor; + gctx->mac_size = descriptor->initial_mac_size; + gctx->digest = GOST_init_digest(descriptor->digest_desc); + gctx->dctx = EVP_MD_CTX_new(); + + if (gctx->digest == NULL + || gctx->dctx == NULL + || EVP_DigestInit_ex(gctx->dctx, gctx->digest, + gctx->provctx->e) <= 0) { + mac_freectx(gctx); + gctx = NULL; + } + } + return gctx; +} + +static void *mac_dupctx(void *vsrc) +{ + GOST_CTX *src = vsrc; + GOST_CTX *dst = + mac_newctx(src->provctx, src->descriptor); + + if (dst != NULL) + EVP_MD_CTX_copy(dst->dctx, src->dctx); + return dst; +} + +static int mac_init(void *mctx, const unsigned char *key, + size_t keylen, const OSSL_PARAM params[]) +{ + GOST_CTX *gctx = mctx; + + return mac_set_ctx_params(gctx, params) + && (key == NULL + || EVP_MD_CTX_ctrl(gctx->dctx, EVP_MD_CTRL_SET_KEY, + (int)keylen, (void *)key) > 0); +} + +static int mac_update(void *mctx, const unsigned char *in, size_t inl) +{ + GOST_CTX *gctx = mctx; + + return EVP_DigestUpdate(gctx->dctx, in, inl) > 0; +} + +static int mac_final(void *mctx, unsigned char *out, size_t *outl, + size_t outsize) +{ + GOST_CTX *gctx = mctx; + unsigned int tmpoutl; + int ret = 0; + + /* This is strange code... but it duplicates pkey_gost_mac_signctx() */ + + if (outl == NULL) + return 0; + + /* for platforms where sizeof(int) != * sizeof(size_t) */ + tmpoutl = *outl; + + if (out != NULL) { + /* We ignore the error for GOST MDs that don't support setting + the size */ + EVP_MD_CTX_ctrl(gctx->dctx, EVP_MD_CTRL_XOF_LEN, gctx->mac_size, NULL); + ret = EVP_DigestFinal_ex(gctx->dctx, out, &tmpoutl); + } + if (outl != NULL) + *outl = (size_t)gctx->mac_size; + return ret; +} + +static const OSSL_PARAM *mac_gettable_params(void *provctx, + const GOST_DESC * descriptor) +{ + static const OSSL_PARAM params[] = { + OSSL_PARAM_size_t("size", NULL), + OSSL_PARAM_size_t("keylen", NULL), + OSSL_PARAM_END + }; + + return params; +} + +static const OSSL_PARAM *mac_gettable_ctx_params(void *mctx, void *provctx) +{ + static const OSSL_PARAM params[] = { + OSSL_PARAM_size_t("size", NULL), + OSSL_PARAM_size_t("keylen", NULL), + OSSL_PARAM_END + }; + + return params; +} + +static const OSSL_PARAM *mac_settable_ctx_params(void *mctx, void *provctx) +{ + static const OSSL_PARAM params[] = { + OSSL_PARAM_size_t("size", NULL), + OSSL_PARAM_octet_string("key", NULL, 0), + OSSL_PARAM_END + }; + + return params; +} + +static int mac_get_params(const GOST_DESC * descriptor, OSSL_PARAM params[]) +{ + OSSL_PARAM *p = NULL; + + if (((p = OSSL_PARAM_locate(params, "size")) != NULL + && !OSSL_PARAM_set_size_t(p, descriptor->initial_mac_size)) + || ((p = OSSL_PARAM_locate(params, "keylen")) != NULL + && !OSSL_PARAM_set_size_t(p, 32))) + return 0; + return 1; +} + +static int mac_get_ctx_params(void *mctx, OSSL_PARAM params[]) +{ + GOST_CTX *gctx = mctx; + OSSL_PARAM *p = NULL; + + if ((p = OSSL_PARAM_locate(params, "size")) != NULL + && !OSSL_PARAM_set_size_t(p, gctx->mac_size)) + return 0; + + if ((p = OSSL_PARAM_locate(params, "keylen")) != NULL) { + unsigned int len = 0; + + if (EVP_MD_CTX_ctrl(gctx->dctx, EVP_MD_CTRL_KEY_LEN, 0, &len) <= 0 + || !OSSL_PARAM_set_size_t(p, len)) + return 0; + } + + if ((p = OSSL_PARAM_locate(params, "xof")) != NULL + && (!(EVP_MD_flags(EVP_MD_CTX_md(gctx->dctx)) & EVP_MD_FLAG_XOF) + || !OSSL_PARAM_set_int(p, gctx->xof_mode))) + return 0; + + return 1; +} + +static int mac_set_ctx_params(void *mctx, const OSSL_PARAM params[]) +{ + GOST_CTX *gctx = mctx; + const OSSL_PARAM *p = NULL; + + if ((p = OSSL_PARAM_locate_const(params, "size")) != NULL + && !OSSL_PARAM_get_size_t(p, &gctx->mac_size)) + return 0; + if ((p = OSSL_PARAM_locate_const(params, "key")) != NULL) { + const unsigned char *key = NULL; + size_t keylen = 0; + int ret; + + if (!OSSL_PARAM_get_octet_string_ptr(p, (const void **)&key, &keylen)) + return 0; + + ret = EVP_MD_CTX_ctrl(gctx->dctx, EVP_MD_CTRL_SET_KEY, + (int)keylen, (void *)key); + if (ret <= 0 && ret != -2) + return 0; + } + if ((p = OSSL_PARAM_locate_const(params, "xof")) != NULL + && (!(EVP_MD_flags(EVP_MD_CTX_md(gctx->dctx)) & EVP_MD_FLAG_XOF) + || !OSSL_PARAM_get_int(p, &gctx->xof_mode))) + return 0; + if ((p = OSSL_PARAM_locate_const(params, "key-mesh")) != NULL) { + size_t key_mesh = 0; + int i_cipher_key_mesh = 0, *p_cipher_key_mesh = NULL; + + if (!OSSL_PARAM_get_size_t(p, &key_mesh)) + return 0; + + if ((p = OSSL_PARAM_locate_const(params, "cipher-key-mesh")) != NULL) { + size_t cipher_key_mesh = 0; + + if (!OSSL_PARAM_get_size_t(p, &cipher_key_mesh)) { + return 0; + } else { + i_cipher_key_mesh = (int)cipher_key_mesh; + p_cipher_key_mesh = &i_cipher_key_mesh; + } + } + + if (EVP_MD_CTX_ctrl(gctx->dctx, EVP_CTRL_KEY_MESH, + key_mesh, p_cipher_key_mesh) <= 0) + return 0; + } + return 1; +} + +/* + * Macros to map the MAC algorithms to their respective GOST_digest + * implementation where necessary. Not needed for magma and grasshopper, as + * they already have fitting names. + */ +#define id_Gost28147_89_MAC_digest Gost28147_89_MAC_digest +#define gost_mac_12_digest Gost28147_89_mac_12_digest +#define id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm_omac_digest \ + kuznyechik_ctracpkm_omac_digest + +typedef void (*fptr_t)(void); +#define MAKE_FUNCTIONS(name, macsize) \ + const GOST_DESC name##_desc = { \ + &name##_digest, \ + macsize, \ + }; \ + static OSSL_FUNC_mac_newctx_fn name##_newctx; \ + static void *name##_newctx(void *provctx) \ + { \ + return mac_newctx(provctx, &name##_desc); \ + } \ + static OSSL_FUNC_mac_gettable_params_fn name##_gettable_params; \ + static const OSSL_PARAM *name##_gettable_params(void *provctx) \ + { \ + return mac_gettable_params(provctx, &name##_desc); \ + } \ + static OSSL_FUNC_mac_get_params_fn name##_get_params; \ + static int name##_get_params(OSSL_PARAM *params) \ + { \ + return mac_get_params(&name##_desc, params); \ + } \ + static const OSSL_DISPATCH name##_functions[] = { \ + { OSSL_FUNC_MAC_GETTABLE_PARAMS, \ + (fptr_t)name##_gettable_params }, \ + { OSSL_FUNC_MAC_GET_PARAMS, (fptr_t)name##_get_params }, \ + { OSSL_FUNC_MAC_NEWCTX, (fptr_t)name##_newctx }, \ + { OSSL_FUNC_MAC_DUPCTX, (fptr_t)mac_dupctx }, \ + { OSSL_FUNC_MAC_FREECTX, (fptr_t)mac_freectx }, \ + { OSSL_FUNC_MAC_INIT, (fptr_t)mac_init }, \ + { OSSL_FUNC_MAC_UPDATE, (fptr_t)mac_update }, \ + { OSSL_FUNC_MAC_FINAL, (fptr_t)mac_final }, \ + { OSSL_FUNC_MAC_GETTABLE_CTX_PARAMS, \ + (fptr_t)mac_gettable_ctx_params }, \ + { OSSL_FUNC_MAC_GET_CTX_PARAMS, (fptr_t)mac_get_ctx_params }, \ + { OSSL_FUNC_MAC_SETTABLE_CTX_PARAMS, \ + (fptr_t)mac_settable_ctx_params }, \ + { OSSL_FUNC_MAC_SET_CTX_PARAMS, (fptr_t)mac_set_ctx_params }, \ + } + +/* + * The name used here is the same as the NID name. Some of the names are + * horribly long, but that can't be helped... + */ +MAKE_FUNCTIONS(id_Gost28147_89_MAC, 4); +MAKE_FUNCTIONS(gost_mac_12, 4); +MAKE_FUNCTIONS(magma_mac, 8); +MAKE_FUNCTIONS(grasshopper_mac, 16); +MAKE_FUNCTIONS(id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm_omac, 16); + +/* The OSSL_ALGORITHM for the provider's operation query function */ +const OSSL_ALGORITHM GOST_prov_macs[] = { + { SN_id_Gost28147_89_MAC ":1.2.643.2.2.22", NULL, + id_Gost28147_89_MAC_functions, "GOST 28147-89 MAC" }, + { SN_gost_mac_12, NULL, gost_mac_12_functions }, + { SN_magma_mac, NULL, magma_mac_functions }, + { SN_grasshopper_mac, NULL, grasshopper_mac_functions }, + { SN_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm_omac + ":1.2.643.7.1.1.5.2.2", NULL, + id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm_omac_functions }, + { NULL , NULL, NULL } +}; + +void GOST_prov_deinit_mac_digests(void) { + static GOST_digest *list[] = { + &Gost28147_89_MAC_digest, + &Gost28147_89_mac_12_digest, + &magma_mac_digest, + &grasshopper_mac_digest, + &kuznyechik_ctracpkm_omac_digest + }; + size_t i; +#define elems(l) (sizeof(l) / sizeof(l[0])) + + for (i = 0; i < elems(list); i++) + GOST_deinit_digest(list[i]); +} diff --git a/test/02-mac.t b/test/02-mac.t index 04a6377..7ecb38c 100644 --- a/test/02-mac.t +++ b/test/02-mac.t @@ -1,8 +1,42 @@ #!/usr/bin/perl use Test2::V0; -skip_all('TODO: add mac support in provider') - unless $ARGV[0] eq 'engine'; -plan(19); + +my $engine_name = $ENV{ENGINE_NAME} || 'gost'; +my $provider_name = $ENV{PROVIDER_NAME} || 'gostprov'; + +# Supported test types: +# +# conf Only if there's a command line argument. +# For this test type, we rely entirely on the +# caller to define the environment variable +# OPENSSL_CONF appropriately. +# standalone-engine-conf Tests the engine through a generated config +# file. +# This is done when there are no command line +# arguments or when the environment variable +# ENGINE_NAME is defined. +# standalone-engine-args Tests the engine through openssl command args. +# This is done when there are no command line +# arguments or when the environment variable +# ENGINE_NAME is defined. +# standalone-provider-conf Tests the provider through a generated config +# file. +# This is done when there are no command line +# arguments or when the environment variable +# PROVIDER_NAME is defined. +# standalone-provider-args Tests the provider through openssl command args. +# This is done when there are no command line +# arguments or when the environment variable +# PROVIDER_NAME is defined. +my @test_types = ( $ARGV[0] ? 'conf' : (), + ( !$ARGV[0] || $ENV{ENGINE_NAME} + ? ( 'standalone-engine-conf', 'standalone-engine-args' ) + : () ), + ( !$ARGV[0] || $ENV{PROVIDER_NAME} + ? ( 'standalone-provider-conf', 'standalone-provider-args' ) + : () ) ); + +plan(19 * scalar @test_types); # prepare data for my $F; @@ -15,38 +49,164 @@ print $F ("12345670" x 8 . "\n") x 4096; close $F; my $key='0123456789abcdef' x 2; +note("\@ARGV = (", join(', ', @ARGV), ")"); +my %configurations = ( + 'conf' => { + 'module-type' => $ARGV[0], + }, + 'standalone-engine-args' => { + 'module-type' => 'engine', + 'openssl-args' => "-engine $engine_name", + }, + 'standalone-provider-args' => { + 'module-type' => 'provider', + 'openssl-args' => "-provider $provider_name -provider default", + }, + 'standalone-engine-conf' => { + 'module-type' => 'engine', + 'openssl-conf' => < { + 'module-type' => 'provider', + 'openssl-conf' => < { + mac_cmd => sub { + my %opts = @_; + my $cmd = "openssl dgst $opts{-args}" + . " -mac $opts{-mac} -macopt key:$opts{-key}" + . (defined $opts{-size} ? " -sigopt size:$opts{-size}" : "") + . " $opts{-infile}"; -# Reopen STDERR to eliminate extra output -open STDERR, ">>","tests.err"; + return $cmd; + }, + check_expected => sub { + my %opts = @_; -is(`openssl dgst -engine ${engine} -mac gost-mac -macopt key:${key} testdata.dat`, -"GOST-MAC-gost-mac(testdata.dat)= 2ee8d13d\n", -"GOST MAC - default size"); + return "$opts{-mac}($opts{-infile})= $opts{-result}\n"; + }, + }, + provider => { + mac_cmd => sub { + my %opts = @_; + my $cmd = "openssl mac $opts{-args} -macopt key:$opts{-key}" + . (defined $opts{-size} ? " -macopt size:$opts{-size}" : "") + . " -in $opts{-infile} $opts{-mac}"; -my $i; -for ($i=1;$i<=8; $i++) { - is(`openssl dgst -engine ${engine} -mac gost-mac -macopt key:${key} -sigopt size:$i testdata.dat`, -"GOST-MAC-gost-mac(testdata.dat)= ".substr("2ee8d13dff7f037d",0,$i*2)."\n", -"GOST MAC - size $i bytes"); -} + return $cmd; + }, + check_expected => sub { + my %opts = @_; + + return uc($opts{-result})."\n"; + }, + }, +); + +foreach my $test_type (@test_types) { + my $configuration = $configurations{$test_type}; + my $module_type = $configuration->{'module-type'}; + my $module_args = $configuration->{'openssl-args'} // ''; + my $module_conf = $configuration->{'openssl-conf'}; + # This is a trick to make a locally modifiable environment variable and + # retain it's current value as a default. + local $ENV{OPENSSL_CONF} = $ENV{OPENSSL_CONF}; + SKIP: { + skip "No module type detected for test type '$test_type'", 19 + unless $module_type; + note("Running tests for test type $test_type"); -is(`openssl dgst -engine ${engine} -mac gost-mac -macopt key:${key} testbig.dat`, -"GOST-MAC-gost-mac(testbig.dat)= 5efab81f\n", -"GOST MAC - big data"); + if ($module_args) { + $module_args = ' ' . $module_args; + } + if (defined $module_conf) { + my $confname = "$test_type.cnf"; + open my $F, '>', $confname; + print $F $module_conf; + close $F; + $ENV{OPENSSL_CONF} = abs_path($confname); + } -is(`openssl dgst -engine ${engine} -mac gost-mac-12 -macopt key:${key} testdata.dat`, -"GOST-MAC-12-gost-mac-12(testdata.dat)= be4453ec\n", -"GOST MAC - parameters 2012"); + # Reopen STDERR to eliminate extra output + #open STDERR, ">>","tests.err"; + my $mac_cmd = $executors{$module_type}->{mac_cmd}; + my $mac_expected = $executors{$module_type}->{check_expected}; + my $cmd; + my $expected; -for ($i=1;$i<=8; $i++) { - is(`openssl dgst -engine ${engine} -mac gost-mac-12 -macopt key:${key} -sigopt size:$i testdata.dat`, -"GOST-MAC-12-gost-mac-12(testdata.dat)= ".substr("be4453ec1ec327be",0,$i*2)."\n", -"GOST MAC parameters 2012 - size $i bytes"); + $cmd = $mac_cmd->(-mac => 'gost-mac', -key => $key, + -args => $module_args, -infile => 'testdata.dat'); + $expected = $mac_expected->(-mac => 'GOST-MAC-gost-mac', + -infile => 'testdata.dat', + -result => '2ee8d13d'); + unless (is(`$cmd`, $expected, "GOST MAC - default size")) { + diag("Command was: $cmd"); + } + + my $i; + for ($i=1;$i<=8; $i++) { + $cmd = $mac_cmd->(-mac => 'gost-mac', -key => $key, -size => $i, + -args => $module_args, -infile => 'testdata.dat'); + $expected = $mac_expected->(-mac => 'GOST-MAC-gost-mac', + -infile => 'testdata.dat', + -result => substr("2ee8d13dff7f037d",0,$i*2)); + unless (is(`$cmd`, $expected, "GOST MAC - size $i bytes")) { + diag("Command was: $cmd"); + } + } + + + + $cmd = $mac_cmd->(-mac => 'gost-mac', -key => $key, + -args => $module_args, -infile => 'testbig.dat'); + $expected = $mac_expected->(-mac => 'GOST-MAC-gost-mac', + -infile => 'testbig.dat', + -result => '5efab81f'); + unless (is(`$cmd`, $expected, "GOST MAC - big data")) { + diag("Command was: $cmd"); + } + + $cmd = $mac_cmd->(-mac => 'gost-mac-12', -key => $key, + -args => $module_args, -infile => 'testdata.dat'); + $expected = $mac_expected->(-mac => 'GOST-MAC-12-gost-mac-12', + -infile => 'testdata.dat', + -result => 'be4453ec'); + unless (is(`$cmd`, $expected, "GOST MAC parameters 2012 - default size")) { + diag("Command was: $cmd"); + } + for ($i=1;$i<=8; $i++) { + $cmd = $mac_cmd->(-mac => 'gost-mac-12', -key => $key, -size => $i, + -args => $module_args, -infile => 'testdata.dat'); + $expected = $mac_expected->(-mac => 'GOST-MAC-12-gost-mac-12', + -infile => 'testdata.dat', + -result => substr("be4453ec1ec327be",0,$i*2)); + unless (is(`$cmd`, $expected, "GOST MAC parameters 2012 - size $i bytes")) { + diag("Command was: $cmd"); + } + } + } } + unlink('testdata.dat'); unlink('testbig.dat'); -- 2.39.5