]> www.wagner.pp.ru Git - openssl-gost/engine.git/blobdiff - gost_grasshopper_cipher.c
gost_grasshopper_cipher: Rework cipher registration
[openssl-gost/engine.git] / gost_grasshopper_cipher.c
index f6b8a39edb62fc472a043c94c6b4c486e05cbbf4..2b3d112fa6e1a7a02876c85fcb13c036feed8644 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Maxim Tishkov 2016
+ * Copyright (c) 2020 Vitaly Chikunov <vt@altlinux.org>
  * This file is distributed under the same license as OpenSSL
  */
 
@@ -7,6 +8,7 @@
 #include "gost_grasshopper_defines.h"
 #include "gost_grasshopper_math.h"
 #include "gost_grasshopper_core.h"
+#include "gost_gost2015.h"
 
 #include <openssl/evp.h>
 #include <openssl/rand.h>
@@ -23,10 +25,11 @@ enum GRASSHOPPER_CIPHER_TYPE {
     GRASSHOPPER_CIPHER_CFB,
     GRASSHOPPER_CIPHER_CTR,
     GRASSHOPPER_CIPHER_CTRACPKM,
+    GRASSHOPPER_CIPHER_CTRACPKMOMAC,
 };
 
-static EVP_CIPHER *gost_grasshopper_ciphers[6] = {
-    NULL, NULL, NULL, NULL, NULL, NULL,
+static EVP_CIPHER *gost_grasshopper_ciphers[7] = {
+    NULL, NULL, NULL, NULL, NULL, NULL, NULL
 };
 
 static GRASSHOPPER_INLINE void
@@ -41,72 +44,184 @@ struct GRASSHOPPER_CIPHER_PARAMS {
     int ctx_size;
     int iv_size;
     bool padding;
+    int extra_flags;
 };
 
-static struct GRASSHOPPER_CIPHER_PARAMS gost_cipher_params[6] = {
- {
-                                NID_grasshopper_ecb,
-                                gost_grasshopper_cipher_init_ecb,
-                                gost_grasshopper_cipher_do_ecb,
-                                NULL,
-                                16,
-                                sizeof(gost_grasshopper_cipher_ctx),
-                                0,
-                                true}
-    ,
-    {
-                                NID_grasshopper_cbc,
-                                gost_grasshopper_cipher_init_cbc,
-                                gost_grasshopper_cipher_do_cbc,
-                                NULL,
-                                16,
-                                sizeof(gost_grasshopper_cipher_ctx),
-                                16,
-                                true}
-    ,
-    {
-                                NID_grasshopper_ofb,
-                                gost_grasshopper_cipher_init_ofb,
-                                gost_grasshopper_cipher_do_ofb,
-                                NULL,
-                                1,
-                                sizeof(gost_grasshopper_cipher_ctx),
-                                16,
-                                false}
-    ,
-    {
-                                NID_grasshopper_cfb,
-                                gost_grasshopper_cipher_init_cfb,
-                                gost_grasshopper_cipher_do_cfb,
-                                NULL,
-                                1,
-                                sizeof(gost_grasshopper_cipher_ctx),
-                                16,
-                                false}
-    ,
-    {
-                                NID_grasshopper_ctr,
-                                gost_grasshopper_cipher_init_ctr,
-                                gost_grasshopper_cipher_do_ctr,
-                                gost_grasshopper_cipher_destroy_ctr,
-                                1,
-                                sizeof(gost_grasshopper_cipher_ctx_ctr),
-                                /* IV size is set to match full block, to make it responsibility of
-                                 * user to assign correct values (IV || 0), and to make naive context
-                                 * copy possible (for software such as openssh) */
-                                16,
-                                false}
-    ,
-    {
-                                     NID_id_tc26_cipher_gostr3412_2015_kuznyechik_ctracpkm,
-                                     gost_grasshopper_cipher_init_ctracpkm,
-                                     gost_grasshopper_cipher_do_ctracpkm,
-                                     gost_grasshopper_cipher_destroy_ctr,
-                                     1,
-                                     sizeof(gost_grasshopper_cipher_ctx_ctr),
-                                     16,
-                                     false}
-    ,
+static GOST_cipher grasshopper_template_cipher = {
+    .block_size = GRASSHOPPER_BLOCK_SIZE,
+    .key_len = GRASSHOPPER_KEY_SIZE,
+    .flags = EVP_CIPH_RAND_KEY |
+        EVP_CIPH_ALWAYS_CALL_INIT,
+    .do_cipher = gost_grasshopper_cipher_do,
+    .cleanup = gost_grasshopper_cipher_cleanup,
+    .ctx_size = sizeof(gost_grasshopper_cipher_ctx),
+    .set_asn1_parameters = gost_grasshopper_set_asn1_parameters,
+    .get_asn1_parameters = gost_grasshopper_get_asn1_parameters,
+    .ctrl = gost_grasshopper_cipher_ctl,
+};
+
+GOST_cipher grasshopper_ecb_cipher = {
+    .nid = NID_grasshopper_ecb,
+    .template = &grasshopper_template_cipher,
+    .flags = EVP_CIPH_ECB_MODE,
+    .init = gost_grasshopper_cipher_init_ecb,
+    .do_cipher = gost_grasshopper_cipher_do_ecb,
+};
+
+GOST_cipher grasshopper_cbc_cipher = {
+    .nid = NID_grasshopper_cbc,
+    .template = &grasshopper_template_cipher,
+    .iv_len = 16,
+    .flags = EVP_CIPH_CBC_MODE |
+        EVP_CIPH_CUSTOM_IV,
+    .init = gost_grasshopper_cipher_init_cbc,
+    .do_cipher = gost_grasshopper_cipher_do_cbc,
+};
+
+GOST_cipher grasshopper_ofb_cipher = {
+    .nid = NID_grasshopper_ofb,
+    .template = &grasshopper_template_cipher,
+    .block_size = 1,
+    .iv_len = 16,
+    .flags = EVP_CIPH_OFB_MODE |
+        EVP_CIPH_NO_PADDING |
+        EVP_CIPH_CUSTOM_IV,
+    .init = gost_grasshopper_cipher_init_ofb,
+    .do_cipher = gost_grasshopper_cipher_do_ofb,
+};
+
+GOST_cipher grasshopper_cfb_cipher = {
+    .nid = NID_grasshopper_cfb,
+    .template = &grasshopper_template_cipher,
+    .block_size = 1,
+    .iv_len = 16,
+    .flags = EVP_CIPH_CFB_MODE |
+        EVP_CIPH_NO_PADDING |
+        EVP_CIPH_CUSTOM_IV,
+    .init = gost_grasshopper_cipher_init_cfb,
+    .do_cipher = gost_grasshopper_cipher_do_cfb,
+};
+
+GOST_cipher grasshopper_ctr_cipher = {
+    .nid = NID_grasshopper_ctr,
+    .template = &grasshopper_template_cipher,
+    .block_size = 1,
+    .iv_len = 8,
+    .flags = EVP_CIPH_CTR_MODE |
+        EVP_CIPH_NO_PADDING |
+        EVP_CIPH_CUSTOM_IV,
+    .init = gost_grasshopper_cipher_init_ctr,
+    .do_cipher = gost_grasshopper_cipher_do_ctr,
+    .ctx_size = sizeof(gost_grasshopper_cipher_ctx_ctr),
+};
+
+GOST_cipher grasshopper_ctr_acpkm_cipher = {
+    .nid = NID_kuznyechik_ctr_acpkm,
+    .template = &grasshopper_template_cipher,
+    .block_size = 1,
+    .iv_len = 8,
+    .flags = EVP_CIPH_CTR_MODE |
+        EVP_CIPH_NO_PADDING |
+        EVP_CIPH_CUSTOM_IV,
+    .init = gost_grasshopper_cipher_init_ctracpkm,
+    .do_cipher = gost_grasshopper_cipher_do_ctracpkm,
+    .ctx_size = sizeof(gost_grasshopper_cipher_ctx_ctr),
+};
+
+GOST_cipher grasshopper_ctr_acpkm_omac_cipher = {
+    .nid = NID_kuznyechik_ctr_acpkm_omac,
+    .template = &grasshopper_template_cipher,
+    .block_size = 1,
+    .iv_len = 8,
+    .flags = EVP_CIPH_CTR_MODE |
+        EVP_CIPH_NO_PADDING |
+        EVP_CIPH_CUSTOM_IV |
+        EVP_CIPH_FLAG_CUSTOM_CIPHER |
+        EVP_CIPH_FLAG_CIPHER_WITH_MAC |
+        EVP_CIPH_CUSTOM_COPY,
+    .init = gost_grasshopper_cipher_init_ctracpkm_omac,
+    .do_cipher = gost_grasshopper_cipher_do_ctracpkm_omac,
+    .ctx_size = sizeof(gost_grasshopper_cipher_ctx_ctr),
+};
+
+static struct GRASSHOPPER_CIPHER_PARAMS gost_cipher_params[7] = {
+       {
+               NID_grasshopper_ecb,
+               gost_grasshopper_cipher_init_ecb,
+               gost_grasshopper_cipher_do_ecb,
+               NULL,
+               16,
+               sizeof(gost_grasshopper_cipher_ctx),
+               0,
+               true,
+               0
+       },
+       {
+               NID_grasshopper_cbc,
+               gost_grasshopper_cipher_init_cbc,
+               gost_grasshopper_cipher_do_cbc,
+               NULL,
+               16,
+               sizeof(gost_grasshopper_cipher_ctx),
+               16,
+               true,
+               0
+       },
+       {
+               NID_grasshopper_ofb,
+               gost_grasshopper_cipher_init_ofb,
+               gost_grasshopper_cipher_do_ofb,
+               NULL,
+               1,
+               sizeof(gost_grasshopper_cipher_ctx),
+               16,
+               false,
+               0
+       },
+       {
+               NID_grasshopper_cfb,
+               gost_grasshopper_cipher_init_cfb,
+               gost_grasshopper_cipher_do_cfb,
+               NULL,
+               1,
+               sizeof(gost_grasshopper_cipher_ctx),
+               16,
+               false,
+               0
+       },
+       {
+               NID_grasshopper_ctr,
+               gost_grasshopper_cipher_init_ctr,
+               gost_grasshopper_cipher_do_ctr,
+               gost_grasshopper_cipher_destroy_ctr,
+               1,
+               sizeof(gost_grasshopper_cipher_ctx_ctr),
+               8,
+               false,
+               0
+       },
+       {
+               NID_kuznyechik_ctr_acpkm,
+               gost_grasshopper_cipher_init_ctracpkm,
+               gost_grasshopper_cipher_do_ctracpkm,
+               gost_grasshopper_cipher_destroy_ctr,
+               1,
+               sizeof(gost_grasshopper_cipher_ctx_ctr),
+               8,
+               false,
+               0
+       },
+       {
+               NID_kuznyechik_ctr_acpkm_omac,
+               gost_grasshopper_cipher_init_ctracpkm_omac,
+               gost_grasshopper_cipher_do_ctracpkm_omac,
+               gost_grasshopper_cipher_destroy_ctr,
+               1,
+               sizeof(gost_grasshopper_cipher_ctx_ctr),
+               8,
+               false,
+               EVP_CIPH_FLAG_CUSTOM_CIPHER|EVP_CIPH_FLAG_CIPHER_WITH_MAC|EVP_CIPH_CUSTOM_COPY
+       },
 };
 
 /* first 256 bit of D from draft-irtf-cfrg-re-keying-12 */
@@ -184,6 +299,9 @@ gost_grasshopper_cipher_destroy_ctr(gost_grasshopper_cipher_ctx * c)
     gost_grasshopper_cipher_ctx_ctr *ctx =
         (gost_grasshopper_cipher_ctx_ctr *) c;
 
+    if (ctx->omac_ctx)
+        EVP_MD_CTX_free(ctx->omac_ctx);
+
     grasshopper_zero128(&ctx->partial_buffer);
 }
 
@@ -195,6 +313,11 @@ int gost_grasshopper_cipher_init(EVP_CIPHER_CTX *ctx,
 
     if (EVP_CIPHER_CTX_get_app_data(ctx) == NULL) {
         EVP_CIPHER_CTX_set_app_data(ctx, EVP_CIPHER_CTX_get_cipher_data(ctx));
+        if (enc && c->type == GRASSHOPPER_CIPHER_CTRACPKM) {
+            gost_grasshopper_cipher_ctx_ctr *ctr = EVP_CIPHER_CTX_get_cipher_data(ctx);
+            if (init_zero_kdf_seed(ctr->kdf_seed) == 0)
+                return -1;
+        }
     }
 
     if (key != NULL) {
@@ -280,6 +403,40 @@ GRASSHOPPER_INLINE int gost_grasshopper_cipher_init_ctracpkm(EVP_CIPHER_CTX
     return gost_grasshopper_cipher_init(ctx, key, iv, enc);
 }
 
+GRASSHOPPER_INLINE int gost_grasshopper_cipher_init_ctracpkm_omac(EVP_CIPHER_CTX
+                                                             *ctx, const unsigned
+                                                             char *key, const unsigned
+                                                             char *iv, int enc)
+{
+       gost_grasshopper_cipher_ctx_ctr *c = EVP_CIPHER_CTX_get_cipher_data(ctx);
+
+       /* NB: setting type makes EVP do_cipher callback useless */
+       c->c.type = GRASSHOPPER_CIPHER_CTRACPKMOMAC;
+       EVP_CIPHER_CTX_set_num(ctx, 0);
+       c->section_size = 4096;
+
+       if (key) {
+               unsigned char cipher_key[32];
+               c->omac_ctx = EVP_MD_CTX_new();
+
+               if (c->omac_ctx == NULL) {
+                   GOSTerr(GOST_F_GOST_GRASSHOPPER_CIPHER_INIT_CTRACPKM_OMAC, ERR_R_MALLOC_FAILURE);
+                               return 0;
+               }
+
+               if (gost2015_acpkm_omac_init(NID_kuznyechik_mac, enc, key,
+                                c->omac_ctx, cipher_key, c->kdf_seed) != 1) {
+                   EVP_MD_CTX_free(c->omac_ctx);
+                               c->omac_ctx = NULL;
+                   return 0;
+               }
+
+               return gost_grasshopper_cipher_init(ctx, cipher_key, iv, enc);
+       }
+
+       return gost_grasshopper_cipher_init(ctx, key, iv, enc);
+}
+
 GRASSHOPPER_INLINE int gost_grasshopper_cipher_do(EVP_CIPHER_CTX *ctx,
                                                   unsigned char *out,
                                                   const unsigned char *in,
@@ -394,19 +551,19 @@ int gost_grasshopper_cipher_do_ctr(EVP_CIPHER_CTX *ctx, unsigned char *out,
     grasshopper_w128_t *currentInputBlock;
     grasshopper_w128_t *currentOutputBlock;
     unsigned int n = EVP_CIPHER_CTX_num(ctx);
-    size_t lasted;
+    size_t lasted = inl;
     size_t i;
     size_t blocks;
     grasshopper_w128_t *iv_buffer;
     grasshopper_w128_t tmp;
 
-    while (n && inl) {
+    while (n && lasted) {
         *(current_out++) = *(current_in++) ^ c->partial_buffer.b[n];
-        --inl;
+        --lasted;
         n = (n + 1) % GRASSHOPPER_BLOCK_SIZE;
     }
     EVP_CIPHER_CTX_set_num(ctx, n);
-    blocks = inl / GRASSHOPPER_BLOCK_SIZE;
+    blocks = lasted / GRASSHOPPER_BLOCK_SIZE;
 
     iv_buffer = (grasshopper_w128_t *) iv;
 
@@ -421,10 +578,9 @@ int gost_grasshopper_cipher_do_ctr(EVP_CIPHER_CTX *ctx, unsigned char *out,
         ctr128_inc(iv_buffer->b);
         current_in += GRASSHOPPER_BLOCK_SIZE;
         current_out += GRASSHOPPER_BLOCK_SIZE;
+                               lasted -= GRASSHOPPER_BLOCK_SIZE;
     }
 
-    // last part
-    lasted = inl - blocks * GRASSHOPPER_BLOCK_SIZE;
     if (lasted > 0) {
         currentInputBlock = (grasshopper_w128_t *) current_in;
         currentOutputBlock = (grasshopper_w128_t *) current_out;
@@ -438,7 +594,7 @@ int gost_grasshopper_cipher_do_ctr(EVP_CIPHER_CTX *ctx, unsigned char *out,
         ctr128_inc(iv_buffer->b);
     }
 
-    return 1;
+    return inl;
 }
 
 #define GRASSHOPPER_BLOCK_MASK (GRASSHOPPER_BLOCK_SIZE - 1)
@@ -460,15 +616,15 @@ int gost_grasshopper_cipher_do_ctracpkm(EVP_CIPHER_CTX *ctx,
     gost_grasshopper_cipher_ctx_ctr *c = EVP_CIPHER_CTX_get_cipher_data(ctx);
     unsigned char *iv = EVP_CIPHER_CTX_iv_noconst(ctx);
     unsigned int num = EVP_CIPHER_CTX_num(ctx);
-    size_t blocks, i, lasted;
+    size_t blocks, i, lasted = inl;
     grasshopper_w128_t tmp;
 
-    while ((num & GRASSHOPPER_BLOCK_MASK) && inl) {
+    while ((num & GRASSHOPPER_BLOCK_MASK) && lasted) {
         *out++ = *in++ ^ c->partial_buffer.b[num & GRASSHOPPER_BLOCK_MASK];
-        --inl;
+        --lasted;
         num++;
     }
-    blocks = inl / GRASSHOPPER_BLOCK_SIZE;
+    blocks = lasted / GRASSHOPPER_BLOCK_SIZE;
 
     // full parts
     for (i = 0; i < blocks; i++) {
@@ -484,10 +640,10 @@ int gost_grasshopper_cipher_do_ctracpkm(EVP_CIPHER_CTX *ctx,
         in += GRASSHOPPER_BLOCK_SIZE;
         out += GRASSHOPPER_BLOCK_SIZE;
         num += GRASSHOPPER_BLOCK_SIZE;
+                               lasted -= GRASSHOPPER_BLOCK_SIZE;
     }
 
     // last part
-    lasted = inl - blocks * GRASSHOPPER_BLOCK_SIZE;
     if (lasted > 0) {
         apply_acpkm_grasshopper(c, &num);
         grasshopper_encrypt_block(&c->c.encrypt_round_keys,
@@ -500,9 +656,35 @@ int gost_grasshopper_cipher_do_ctracpkm(EVP_CIPHER_CTX *ctx,
     }
     EVP_CIPHER_CTX_set_num(ctx, num);
 
-    return 1;
+    return inl;
 }
 
+int gost_grasshopper_cipher_do_ctracpkm_omac(EVP_CIPHER_CTX *ctx,
+                                        unsigned char *out,
+                                        const unsigned char *in, size_t inl)
+{
+       int result;
+  gost_grasshopper_cipher_ctx_ctr *c = EVP_CIPHER_CTX_get_cipher_data(ctx);
+       /* As in and out can be the same pointer, process unencrypted here */
+       if (EVP_CIPHER_CTX_encrypting(ctx))
+               EVP_DigestSignUpdate(c->omac_ctx, in, inl);
+
+       if (in == NULL && inl == 0) { /* Final call */
+               return gost2015_final_call(ctx, c->omac_ctx, KUZNYECHIK_MAC_MAX_SIZE, c->tag, gost_grasshopper_cipher_do_ctracpkm);
+       }
+
+  if (in == NULL) {
+      GOSTerr(GOST_F_GOST_GRASSHOPPER_CIPHER_DO_CTRACPKM_OMAC, ERR_R_EVP_LIB);
+      return -1;
+  }
+       result = gost_grasshopper_cipher_do_ctracpkm(ctx, out, in, inl);
+
+       /* As in and out can be the same pointer, process decrypted here */
+       if (!EVP_CIPHER_CTX_encrypting(ctx))
+               EVP_DigestSignUpdate(c->omac_ctx, out, inl);
+
+       return result;
+}
 /*
  * Fixed 128-bit IV implementation make shift regiser redundant.
  */
@@ -690,36 +872,43 @@ int gost_grasshopper_cipher_cleanup(EVP_CIPHER_CTX *ctx)
 
 int gost_grasshopper_set_asn1_parameters(EVP_CIPHER_CTX *ctx, ASN1_TYPE *params)
 {
-    int len = 0;
-    unsigned char *buf = NULL;
-    ASN1_OCTET_STRING *os = ASN1_OCTET_STRING_new();
+       if (EVP_CIPHER_CTX_mode(ctx) == EVP_CIPH_CTR_MODE) {
+               gost_grasshopper_cipher_ctx_ctr *ctr = EVP_CIPHER_CTX_get_cipher_data(ctx);
 
-    if (!os || !ASN1_OCTET_STRING_set(os, buf, len)) {
-        ASN1_OCTET_STRING_free(os);
-        OPENSSL_free(buf);
-        GOSTerr(GOST_F_GOST_GRASSHOPPER_SET_ASN1_PARAMETERS,
-                ERR_R_MALLOC_FAILURE);
-        return 0;
-    }
-    OPENSSL_free(buf);
+               /* CMS implies 256kb section_size */
+               ctr->section_size = 256*1024;
 
-    ASN1_TYPE_set(params, V_ASN1_SEQUENCE, os);
-    return 1;
+               return gost2015_set_asn1_params(params, EVP_CIPHER_CTX_original_iv(ctx), 8,
+                               ctr->kdf_seed);
+       }
+       return 0;
 }
 
 GRASSHOPPER_INLINE int gost_grasshopper_get_asn1_parameters(EVP_CIPHER_CTX
                                                             *ctx, ASN1_TYPE
                                                             *params)
 {
-    if (ASN1_TYPE_get(params) != V_ASN1_SEQUENCE) {
-        return -1;
-    }
+       if (EVP_CIPHER_CTX_mode(ctx) == EVP_CIPH_CTR_MODE) {
+               gost_grasshopper_cipher_ctx_ctr *ctr = EVP_CIPHER_CTX_get_cipher_data(ctx);
 
-    return 1;
+               int iv_len = 16;
+               unsigned char iv[16];
+
+               if (gost2015_get_asn1_params(params, 16, iv, 8, ctr->kdf_seed) == 0) {
+                       return 0;
+               }
+
+               memcpy(EVP_CIPHER_CTX_iv_noconst(ctx), iv, iv_len);
+               memcpy((unsigned char *)EVP_CIPHER_CTX_original_iv(ctx), iv, iv_len);
+
+               /* CMS implies 256kb section_size */
+               ctr->section_size = 256*1024;
+               return 1;
+       }
+       return 0;
 }
 
-int gost_grasshopper_cipher_ctl(EVP_CIPHER_CTX *ctx, int type, int arg,
-                                void *ptr)
+int gost_grasshopper_cipher_ctl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr)
 {
     switch (type) {
     case EVP_CTRL_RAND_KEY:{
@@ -733,7 +922,9 @@ int gost_grasshopper_cipher_ctl(EVP_CIPHER_CTX *ctx, int type, int arg,
     case EVP_CTRL_KEY_MESH:{
             gost_grasshopper_cipher_ctx_ctr *c =
                 EVP_CIPHER_CTX_get_cipher_data(ctx);
-            if (c->c.type != GRASSHOPPER_CIPHER_CTRACPKM || !arg
+            if ((c->c.type != GRASSHOPPER_CIPHER_CTRACPKM &&
+                                                   c->c.type != GRASSHOPPER_CIPHER_CTRACPKMOMAC)
+                                                   || (arg == 0)
                 || (arg % GRASSHOPPER_BLOCK_SIZE))
                 return -1;
             c->section_size = arg;
@@ -793,6 +984,60 @@ int gost_grasshopper_cipher_ctl(EVP_CIPHER_CTX *ctx, int type, int arg,
         }
         return -1;
 #endif
+#if 0
+    case EVP_CTRL_AEAD_GET_TAG:
+    case EVP_CTRL_AEAD_SET_TAG:
+        {
+            int taglen = arg;
+            unsigned char *tag = ptr;
+
+            gost_grasshopper_cipher_ctx *c = EVP_CIPHER_CTX_get_cipher_data(ctx);
+            if (c->c.type != GRASSHOPPER_CIPHER_MGM)
+                return -1;
+
+            if (taglen > KUZNYECHIK_MAC_MAX_SIZE) {
+                CRYPTOCOMerr(CRYPTOCOM_F_GOST_GRASSHOPPER_CIPHER_CTL,
+                        CRYPTOCOM_R_INVALID_TAG_LENGTH);
+                return -1;
+            }
+
+            if (type == EVP_CTRL_AEAD_GET_TAG)
+                memcpy(tag, c->final_tag, taglen);
+            else
+                memcpy(c->final_tag, tag, taglen);
+
+            return 1;
+        }
+#endif
+               case EVP_CTRL_PROCESS_UNPROTECTED:
+    {
+      STACK_OF(X509_ATTRIBUTE) *x = ptr;
+      gost_grasshopper_cipher_ctx_ctr *c = EVP_CIPHER_CTX_get_cipher_data(ctx);
+
+      if (c->c.type != GRASSHOPPER_CIPHER_CTRACPKMOMAC)
+        return -1;
+
+      return gost2015_process_unprotected_attributes(x, arg, KUZNYECHIK_MAC_MAX_SIZE, c->tag);
+    }
+    return 1;
+    case EVP_CTRL_COPY: {
+                       EVP_CIPHER_CTX *out = ptr;
+
+      gost_grasshopper_cipher_ctx_ctr *out_cctx = EVP_CIPHER_CTX_get_cipher_data(out);
+      gost_grasshopper_cipher_ctx_ctr *in_cctx  = EVP_CIPHER_CTX_get_cipher_data(ctx);
+
+      if (in_cctx->c.type != GRASSHOPPER_CIPHER_CTRACPKMOMAC)
+          return -1;
+
+                       if (in_cctx->omac_ctx == out_cctx->omac_ctx) {
+                               out_cctx->omac_ctx = EVP_MD_CTX_new();
+                               if (out_cctx->omac_ctx == NULL) {
+                                       GOSTerr(GOST_F_GOST_GRASSHOPPER_CIPHER_CTL, ERR_R_MALLOC_FAILURE);
+                                       return -1;
+                               }
+                       }
+                       return EVP_MD_CTX_copy(out_cctx->omac_ctx, in_cctx->omac_ctx);
+               }
     default:
         GOSTerr(GOST_F_GOST_GRASSHOPPER_CIPHER_CTL,
                 GOST_R_UNSUPPORTED_CIPHER_CTL_COMMAND);
@@ -810,20 +1055,16 @@ GRASSHOPPER_INLINE EVP_CIPHER *cipher_gost_grasshopper_create(int
 }
 
 const int cipher_gost_grasshopper_setup(EVP_CIPHER *cipher, uint8_t mode,
-                                        int iv_size, bool padding)
+                                        int iv_size, bool padding, int extra_flags)
 {
+       unsigned long flags = (unsigned long)(mode
+                | ((!padding) ?  EVP_CIPH_NO_PADDING : 0)
+                                        | ((iv_size >  0) ?  EVP_CIPH_CUSTOM_IV : 0)
+                                        | EVP_CIPH_RAND_KEY |  EVP_CIPH_ALWAYS_CALL_INIT
+                                        | extra_flags);
+
     return EVP_CIPHER_meth_set_iv_length(cipher, iv_size)
-        && EVP_CIPHER_meth_set_flags(cipher,
-                                     (unsigned long)(mode |
-                                                     ((!padding) ?
-                                                      EVP_CIPH_NO_PADDING :
-                                                      0) | ((iv_size >
-                                                             0) ?
-                                                            EVP_CIPH_CUSTOM_IV
-                                                            : 0) |
-                                                     EVP_CIPH_RAND_KEY |
-                                                     EVP_CIPH_ALWAYS_CALL_INIT)
-        )
+        && EVP_CIPHER_meth_set_flags(cipher, flags)
         && EVP_CIPHER_meth_set_cleanup(cipher, gost_grasshopper_cipher_cleanup)
         && EVP_CIPHER_meth_set_set_asn1_params(cipher,
                                                gost_grasshopper_set_asn1_parameters)
@@ -843,7 +1084,7 @@ const GRASSHOPPER_INLINE EVP_CIPHER *cipher_gost_grasshopper(uint8_t mode,
 
     if (*cipher == NULL) {
         grasshopper_init_cipher_func init_cipher;
-        int nid, block_size, ctx_size, iv_size;
+        int nid, block_size, ctx_size, iv_size, extra_flags;
         bool padding;
 
         params = &gost_cipher_params[num];
@@ -854,13 +1095,14 @@ const GRASSHOPPER_INLINE EVP_CIPHER *cipher_gost_grasshopper(uint8_t mode,
         ctx_size = params->ctx_size;
         iv_size = params->iv_size;
         padding = params->padding;
+                               extra_flags = params->extra_flags;
 
         *cipher = cipher_gost_grasshopper_create(nid, block_size);
         if (*cipher == NULL) {
             return NULL;
         }
 
-        if (!cipher_gost_grasshopper_setup(*cipher, mode, iv_size, padding)
+        if (!cipher_gost_grasshopper_setup(*cipher, mode, iv_size, padding, extra_flags)
             || !EVP_CIPHER_meth_set_init(*cipher, init_cipher)
             || !EVP_CIPHER_meth_set_impl_ctx_size(*cipher, ctx_size)) {
             EVP_CIPHER_meth_free(*cipher);
@@ -902,6 +1144,12 @@ const GRASSHOPPER_INLINE EVP_CIPHER *cipher_gost_grasshopper_ctracpkm()
                                    GRASSHOPPER_CIPHER_CTRACPKM);
 }
 
+const GRASSHOPPER_INLINE EVP_CIPHER *cipher_gost_grasshopper_ctracpkm_omac()
+{
+    return cipher_gost_grasshopper(EVP_CIPH_CTR_MODE,
+                                   GRASSHOPPER_CIPHER_CTRACPKMOMAC);
+}
+
 void cipher_gost_grasshopper_destroy(void)
 {
     EVP_CIPHER_meth_free(gost_grasshopper_ciphers[GRASSHOPPER_CIPHER_ECB]);
@@ -916,4 +1164,7 @@ void cipher_gost_grasshopper_destroy(void)
     gost_grasshopper_ciphers[GRASSHOPPER_CIPHER_CTR] = NULL;
     EVP_CIPHER_meth_free(gost_grasshopper_ciphers[GRASSHOPPER_CIPHER_CTRACPKM]);
     gost_grasshopper_ciphers[GRASSHOPPER_CIPHER_CTRACPKM] = NULL;
+    EVP_CIPHER_meth_free(gost_grasshopper_ciphers[GRASSHOPPER_CIPHER_CTRACPKMOMAC]);
+    gost_grasshopper_ciphers[GRASSHOPPER_CIPHER_CTRACPKMOMAC] = NULL;
 }
+/* vim: set expandtab cinoptions=\:0,l1,t0,g0,(0 sw=4 : */