diff --git a/gost_crypt.c b/gost_crypt.c index 58848de6..117308e3 100644 --- a/gost_crypt.c +++ b/gost_crypt.c @@ -427,11 +427,18 @@ static int magma_cipher_init(GOST_cipher_ctx *ctx, const unsigned char *key, const unsigned char *iv, int enc) { struct ossl_gost_cipher_ctx *c = GOST_cipher_ctx_get_cipher_data(ctx); + int key_meshing = c->key_meshing; + int key_meshing_set = c->key_meshing_set; + /* FIXME this is just initializtion check */ if (GOST_cipher_ctx_get_app_data(ctx) == NULL) { if (!gost_cipher_set_param(c, NID_id_tc26_gost_28147_param_Z)) return 0; GOST_cipher_ctx_set_app_data(ctx, GOST_cipher_ctx_get_cipher_data(ctx)); + if (key_meshing_set) { + c->key_meshing = key_meshing; + c->key_meshing_set = key_meshing_set; + } if (enc) { if (init_zero_kdf_seed(c->kdf_seed) == 0) @@ -452,9 +459,11 @@ static int magma_cipher_init(GOST_cipher_ctx *ctx, const unsigned char *key, if (GOST_cipher_ctx_nid(ctx) == NID_magma_ctr_acpkm || GOST_cipher_ctx_nid(ctx) == NID_magma_ctr_acpkm_omac) { - c->key_meshing = 1024; + if (!c->key_meshing_set) + c->key_meshing = 1024; } else { c->key_meshing = 0; + c->key_meshing_set = 0; } c->count = 0; @@ -1155,6 +1164,7 @@ static int gost_cipher_ctl(GOST_cipher_ctx *ctx, int type, int arg, void *ptr) GOST_cipher_ctx_get_cipher_data(ctx); int nid; int cur_meshing; + int cur_meshing_set; int ret; if (c == NULL) { @@ -1171,8 +1181,10 @@ static int gost_cipher_ctl(GOST_cipher_ctx *ctx, int type, int arg, void *ptr) } cur_meshing = c->key_meshing; + cur_meshing_set = c->key_meshing_set; ret = gost_cipher_set_param(c, nid); c->key_meshing = cur_meshing; + c->key_meshing_set = cur_meshing_set; return ret; } else { return 0; @@ -1191,6 +1203,7 @@ static int gost_cipher_ctl(GOST_cipher_ctx *ctx, int type, int arg, void *ptr) } c->key_meshing = arg; + c->key_meshing_set = 1; return 1; } default: @@ -1255,11 +1268,12 @@ static int magma_cipher_ctl(GOST_cipher_ctx *ctx, int type, int arg, void *ptr) struct ossl_gost_cipher_ctx *c = GOST_cipher_ctx_get_cipher_data(ctx); - if (c == NULL) { + if (c == NULL || arg < 0) { return -1; } c->key_meshing = arg; + c->key_meshing_set = 1; return 1; } case EVP_CTRL_TLSTREE: @@ -1472,6 +1486,7 @@ static int magma_set_asn1_parameters (GOST_cipher_ctx *ctx, ASN1_TYPE *params) { struct ossl_gost_cipher_ctx *c = GOST_cipher_ctx_get_cipher_data(ctx); c->key_meshing = 8192; + c->key_meshing_set = 1; /* * Same lazy kdf_seed init as in gost_grasshopper_set_asn1_parameters: @@ -1493,6 +1508,7 @@ static int magma_get_asn1_parameters(GOST_cipher_ctx *ctx, ASN1_TYPE *params) unsigned char iv[16]; c->key_meshing = 8192; + c->key_meshing_set = 1; if (gost2015_get_asn1_params(params, MAGMA_UKM_LEN, iv, 4, c->kdf_seed) == 0) return -1; diff --git a/gost_grasshopper_cipher.c b/gost_grasshopper_cipher.c index 3414b1c9..93116d3a 100644 --- a/gost_grasshopper_cipher.c +++ b/gost_grasshopper_cipher.c @@ -334,7 +334,8 @@ gost_grasshopper_cipher_init_ctracpkm(GOST_cipher_ctx /* NB: setting type makes EVP do_cipher callback useless */ c->c.type = GRASSHOPPER_CIPHER_CTRACPKM; GOST_cipher_ctx_set_num(ctx, 0); - c->section_size = 4096; + if (!c->section_size_set) + c->section_size = 4096; return gost_grasshopper_cipher_init(ctx, key, iv, enc); } @@ -350,7 +351,8 @@ gost_grasshopper_cipher_init_ctracpkm_omac(GOST_cipher_ctx /* NB: setting type makes EVP do_cipher callback useless */ c->c.type = GRASSHOPPER_CIPHER_CTRACPKMOMAC; GOST_cipher_ctx_set_num(ctx, 0); - c->section_size = 4096; + if (!c->section_size_set) + c->section_size = 4096; if (key) { unsigned char cipher_key[32]; @@ -1005,6 +1007,7 @@ static int gost_grasshopper_set_asn1_parameters(GOST_cipher_ctx *ctx, ASN1_TYPE /* CMS implies 256kb section_size */ ctr->section_size = 256*1024; + ctr->section_size_set = 1; return gost2015_set_asn1_params(params, GOST_cipher_ctx_original_iv(ctx), 8, ctr->kdf_seed); @@ -1030,6 +1033,7 @@ gost_grasshopper_get_asn1_parameters(GOST_cipher_ctx *ctx, ASN1_TYPE *params) /* CMS implies 256kb section_size */ ctr->section_size = 256*1024; + ctr->section_size_set = 1; return 1; } return 0; @@ -1167,10 +1171,11 @@ static int gost_grasshopper_cipher_ctl(GOST_cipher_ctx *ctx, int type, int arg, GOST_cipher_ctx_get_cipher_data(ctx); if ((c->c.type != GRASSHOPPER_CIPHER_CTRACPKM && c->c.type != GRASSHOPPER_CIPHER_CTRACPKMOMAC) - || (arg == 0) + || (arg <= 0) || (arg % GRASSHOPPER_BLOCK_SIZE)) return -1; c->section_size = arg; + c->section_size_set = 1; break; } case EVP_CTRL_TLSTREE: diff --git a/gost_grasshopper_cipher.h b/gost_grasshopper_cipher.h index 6c47a0b9..fc38caf4 100644 --- a/gost_grasshopper_cipher.h +++ b/gost_grasshopper_cipher.h @@ -34,6 +34,7 @@ typedef struct { grasshopper_w128_t partial_buffer; unsigned int section_size; /* After how much bytes mesh the key, if 0 never mesh and work like plain ctr. */ + unsigned int section_size_set; unsigned char kdf_seed[8]; unsigned char tag[16]; EVP_MD_CTX *omac_ctx; diff --git a/gost_lcl.h b/gost_lcl.h index c106a6a8..5f18e083 100644 --- a/gost_lcl.h +++ b/gost_lcl.h @@ -217,6 +217,7 @@ struct ossl_gost_cipher_ctx { int paramNID; unsigned int count; int key_meshing; + int key_meshing_set; unsigned char kdf_seed[8]; unsigned char tag[8]; gost_ctx cctx; @@ -387,4 +388,3 @@ extern GOST_NID_JOB kuznyechik_mgm_NID; #endif /* vim: set expandtab cinoptions=\:0,l1,t0,g0,(0 sw=4 : */ - diff --git a/test_ciphers.c b/test_ciphers.c index 5becf69e..7508a82c 100644 --- a/test_ciphers.c +++ b/test_ciphers.c @@ -18,6 +18,9 @@ #include #include #include +#include +#include "gost_lcl.h" +#include "gost_grasshopper_cipher.h" #if defined _MSC_VER # include @@ -49,6 +52,8 @@ #ifdef __GNUC__ /* Pragma to allow commenting out some tests. */ # pragma GCC diagnostic ignored "-Wunused-const-variable" +/* gost_grasshopper_cipher.h declares static functions it never defines. */ +# pragma GCC diagnostic ignored "-Wunused-function" #endif /* @@ -580,6 +585,181 @@ static int test_provider_alg_id_param(const EVP_CIPHER *type, const char *name, return ret | test; } +#define ACPKM_CHECK(e) \ + do { \ + if (!(e)) { \ + ERR_print_errors_fp(stderr); \ + fprintf(stderr, "%s:%d: check failed: %s\n", \ + __FILE__, __LINE__, #e); \ + fail = 1; \ + goto end; \ + } \ + } while (0) + +static int is_kuz_name(const char *name) +{ + return strcmp(name, SN_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm) == 0; +} + +/* Read the ACPKM section size (bytes) and its "explicitly set" flag. */ +static void get_acpkm_section(EVP_CIPHER_CTX *ctx, int is_kuz, + size_t *section, int *is_set) +{ + if (is_kuz) { + const gost_grasshopper_cipher_ctx_ctr *c = + EVP_CIPHER_CTX_get_cipher_data(ctx); + *section = c->section_size; + *is_set = c->section_size_set; + } else { + const struct ossl_gost_cipher_ctx *c = + EVP_CIPHER_CTX_get_cipher_data(ctx); + *section = (size_t)c->key_meshing; + *is_set = c->key_meshing_set; + } +} + +static int test_engine_asn1_acpkm_reinit_len(const EVP_CIPHER *type, + const char *name, int is_kuz, + size_t boundary, size_t len, + const unsigned char *key, + const unsigned char *iv) +{ + EVP_CIPHER_CTX *enc = EVP_CIPHER_CTX_new(); + EVP_CIPHER_CTX *dec = EVP_CIPHER_CTX_new(); + ASN1_TYPE *params = ASN1_TYPE_new(); + unsigned char *pt = OPENSSL_malloc(len); + unsigned char *ct = OPENSSL_malloc(len + 16); + unsigned char *out = OPENSSL_malloc(len + 16); + size_t section = 0, split, i; + int is_set = 0, outlen = 0, tmplen = 0, total = 0, fail = 0; + + printf("Engine ASN1 ACPKM reinit test [%s, %lu bytes]: ", name, (unsigned long)len); + ACPKM_CHECK(enc && dec && params && pt && ct && out); + for (i = 0; i < len; i++) + pt[i] = (unsigned char)(i * 7 + 1); + + ACPKM_CHECK(EVP_CipherInit_ex(enc, type, NULL, key, iv, 1)); + ACPKM_CHECK(EVP_CIPHER_param_to_asn1(enc, params) > 0); + get_acpkm_section(enc, is_kuz, §ion, &is_set); + ACPKM_CHECK(section == boundary && is_set); + ACPKM_CHECK(EVP_CipherUpdate(enc, ct, &outlen, pt, (int)len)); + total = outlen; + ACPKM_CHECK(EVP_CipherFinal_ex(enc, ct + total, &tmplen)); + total += tmplen; + ACPKM_CHECK((size_t)total == len); + + ACPKM_CHECK(EVP_CipherInit_ex(dec, type, NULL, NULL, NULL, 0)); + ACPKM_CHECK(EVP_CIPHER_asn1_to_param(dec, params) > 0); + get_acpkm_section(dec, is_kuz, §ion, &is_set); + ACPKM_CHECK(section == boundary && is_set); + ACPKM_CHECK(EVP_CipherInit_ex(dec, NULL, NULL, key, NULL, 0)); + /* The value must survive the key (re)initialization. */ + get_acpkm_section(dec, is_kuz, §ion, &is_set); + ACPKM_CHECK(section == boundary && is_set); + + /* Feed data split right before the section border when it is reached. */ + split = len > boundary ? boundary - 1 : len / 2; + ACPKM_CHECK(EVP_CipherUpdate(dec, out, &outlen, ct, (int)split)); + total = outlen; + ACPKM_CHECK(EVP_CipherUpdate(dec, out + total, &outlen, ct + split, + (int)(len - split))); + total += outlen; + ACPKM_CHECK(EVP_CipherFinal_ex(dec, out + total, &tmplen)); + total += tmplen; + ACPKM_CHECK((size_t)total == len && memcmp(out, pt, len) == 0); + +end: + if (fail) + printf(cRED "Test FAILED" cNORM "\n"); + else + printf(cGREEN "Test passed" cNORM "\n"); + EVP_CIPHER_CTX_free(enc); + EVP_CIPHER_CTX_free(dec); + ASN1_TYPE_free(params); + OPENSSL_free(pt); + OPENSSL_free(ct); + OPENSSL_free(out); + return fail; +} + +static int test_engine_acpkm_invalid_section(const EVP_CIPHER *type, + const char *name, int is_kuz, + const unsigned char *key, + const unsigned char *iv) +{ + static const int bad_kuz[] = { 0, 1, 17, -16, INT_MIN }; + static const int bad_magma[] = { -1, INT_MIN }; + const int *bad = is_kuz ? bad_kuz : bad_magma; + const size_t nbad = is_kuz ? sizeof(bad_kuz) / sizeof(bad_kuz[0]) + : sizeof(bad_magma) / sizeof(bad_magma[0]); + const int good = is_kuz ? 64 : 2048; + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + unsigned char buf[64] = { 0 }, res[64 + 16]; + size_t before, after, i; + int before_set, after_set, outlen = 0, fail = 0; + + printf("Engine ACPKM invalid section size test [%s]: ", name); + ACPKM_CHECK(ctx != NULL); + ACPKM_CHECK(EVP_CipherInit_ex(ctx, type, NULL, key, iv, 1)); + get_acpkm_section(ctx, is_kuz, &before, &before_set); + + for (i = 0; i < nbad; i++) { + ACPKM_CHECK(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_KEY_MESH, bad[i], NULL) <= 0); + ERR_clear_error(); + get_acpkm_section(ctx, is_kuz, &after, &after_set); + ACPKM_CHECK(after == before && after_set == before_set); + } + /* The context must still be usable after the rejected values. */ + ACPKM_CHECK(EVP_CipherUpdate(ctx, res, &outlen, buf, sizeof(buf))); + + /* Fresh context: a valid value is accepted and stored as is. */ + EVP_CIPHER_CTX_free(ctx); + ctx = EVP_CIPHER_CTX_new(); + ACPKM_CHECK(ctx != NULL); + ACPKM_CHECK(EVP_CipherInit_ex(ctx, type, NULL, key, iv, 1)); + ACPKM_CHECK(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_KEY_MESH, good, NULL) > 0); + get_acpkm_section(ctx, is_kuz, &after, &after_set); + ACPKM_CHECK(after == (size_t)good && after_set); + +end: + if (fail) + printf(cRED "Test FAILED" cNORM "\n"); + else + printf(cGREEN "Test passed" cNORM "\n"); + EVP_CIPHER_CTX_free(ctx); + return fail; +} + +static int test_engine_asn1_acpkm_reinit(const EVP_CIPHER *type, const char *name, + const unsigned char *key, + const unsigned char *iv) +{ + int is_kuz, ret = 0; + /* Section size implied by CMS (RFC 9189 / R 1323565.1.017) */ + const size_t boundary = is_kuz_name(name) ? 256 * 1024 : 8192; + const size_t step = is_kuz_name(name) ? 16 : 8; + size_t lens[6]; + size_t i; + + if (EVP_CIPHER_get0_provider(type) != NULL + || (!is_kuz_name(name) && strcmp(name, SN_magma_ctr_acpkm) != 0)) + return 0; + is_kuz = is_kuz_name(name); + + lens[0] = 8192 + step; + lens[1] = boundary - step; + lens[2] = boundary - 1; + lens[3] = boundary; + lens[4] = boundary + 1; + lens[5] = boundary + step; + + for (i = 0; i < sizeof(lens) / sizeof(lens[0]); i++) + ret |= test_engine_asn1_acpkm_reinit_len(type, name, is_kuz, boundary, + lens[i], key, iv); + ret |= test_engine_acpkm_invalid_section(type, name, is_kuz, key, iv); + return ret; +} + static int test_provider_padded_decrypt_case(const EVP_CIPHER *type, const char *name, const unsigned char *key, @@ -784,9 +964,22 @@ int main(int argc, char **argv) ret |= test_provider_padded_decrypt(ciph, t->algname, t->block, t->key, t->iv); } + ret |= test_engine_asn1_acpkm_reinit(ciph, t->algname, t->key, t->iv); EVP_CIPHER_free(ciph); } + { + EVP_CIPHER *ciph; + + ERR_set_mark(); + T((ciph = (EVP_CIPHER *)EVP_get_cipherbyname(SN_magma_ctr_acpkm)) + || (ciph = EVP_CIPHER_fetch(NULL, SN_magma_ctr_acpkm, NULL))); + ERR_pop_to_mark(); + + ret |= test_engine_asn1_acpkm_reinit(ciph, SN_magma_ctr_acpkm, + Km, iv_ctr); + EVP_CIPHER_free(ciph); + } warn_all_untested();