Hi, Georg!

On Nov 03, Georg Richter wrote:
> Hello Serg,
> 
> attached you will find the patch for MDEV-9069.
> 
> /Georg
> 
> -- 
> Georg Richter, Senior Software Engineer
> MariaDB Corporation Ab

>  From bde4b9d0683f115fa55bb6d2e2164739f89d732c Mon Sep 17 00:00:00 2001
>  From: Georg Richter <[email protected]>
>  Date: Thu, 3 Nov 2016 07:44:15 +0100
>  Subject: [PATCH] Initial implementaion for MDEV-9069: "extend AES_ENCRYPT()
>  and AES_DECRYPT() to support IV and the algorithm"
> 
> - Extended function syntax:
>   AES_ENCRYPT(plaintext, key, [[iv, [aad]] using block_encryption_mode);
>   AES_DECRYPT(plaintext, key, [[iv, [aad]] using block_encryption_mode);

This doesn't look right. at least square brackets aren't paired,
but also I'd expect [using block_encryption_mode] to be in brackets,
and the comma after key should be too.

>   AES_ crypt functions will return an error, if
>   - length of IV is too small
>   - the number of parameter doesn't match (e.g. if an iv was specified for 
> block cipher mode ECB)
>   AES_crypt functions will return a warning, if
>   - key_size is too small
> 
> - Added new session variable block_encryption_mode
> - Added a new status variable block_encryption_mode_list, which lists 
> available block_encryption modes

This is absolutely not necessary, the list of available values for
block_encryption_mode can be retrieved as

 select enum_value_list from system_variables
   where variable_name='block_encryption_mode';

> 
> - Added new tests for AES_ crypt functions: AES_ECB and AES_CBC vectors from
>   NIST Cryptographic validation program (CAVP)
>   [http://csrc.nist.gov/groups/STM/cavp/]

What license are these tests available under?

> 
> - To prevent too many warnings all keys in tests now are hased by sha2() 
> function

What does that mean? That is, no need to explain, I'll see it from the code,
but the commit comment is confusing, please reformulate this sentence.

> 
> - This patch also includes bug fix for MDEV-11174 (my_aes_crypt crashes in 
> GCM mode)
> 
>  Note:
> - AES_GCM and AES_CTR are not well tested - CAVP test vectors couldn't be 
> used for AES_GCM since the auth tag after encryption differs.

why does it differ?

> - A key which is longer than the requested key size will be truncated to the 
> appropriate size.

what do you mean, truncated, there is a key derivation
function that creates the real key from the user specified long key
in Item_aes_crypt::create_key

> diff --git a/mysys_ssl/my_crypt.cc b/mysys_ssl/my_crypt.cc
> index 49bd9af..5485576 100644
> --- a/mysys_ssl/my_crypt.cc
> +++ b/mysys_ssl/my_crypt.cc
> @@ -155,6 +155,7 @@ class MyCTX_gcm : public MyCTX
>      int real_ivlen= EVP_CIPHER_CTX_iv_length(&ctx);
>      aad= iv + real_ivlen;
>      aadlen= ivlen - real_ivlen;
> +    EVP_CIPHER_CTX_ctrl(&ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL);

Hmm, why is that needed?

>      return res;
>    }
>  
> @@ -205,12 +208,56 @@ const EVP_CIPHER *(*ciphers[])(uint)= {
>      aes_ecb, aes_cbc
>  #ifdef HAVE_EncryptAes128Ctr
>    , aes_ctr
> +#endif
>  #ifdef HAVE_EncryptAes128Gcm
>    , aes_gcm
>  #endif
> +};
> +
> +const char *my_aes_block_encryption_mode_str[]= {
> +  "aes-128-ecb",
> +  "aes-192-ecb",
> +  "aes-256-ecb",
> +  "aes-128-cbc",
> +  "aes-192-cbc",
> +  "aes-256-cbc"
> +#ifdef HAVE_EncryptAes128Ctr
> + , "aes-128-ctr",
> +  "aes-192-ctr",
> +  "aes-256-ctr"
>  #endif
> +#ifdef HAVE_EncryptAes128Gcm
> + , "aes-128-gcm",
> +  "aes-192-gcm",
> +  "aes-256-gcm"
> +#endif
> +  , NULL
>  };
>  
> +struct st_my_aes_info my_aes_info[]= {
> +  { my_aes_128_ecb, MY_AES_ECB, 128, 0, 2},
> +  { my_aes_192_ecb, MY_AES_ECB, 192, 0, 2},
> +  { my_aes_256_ecb, MY_AES_ECB, 256, 0, 2},
> +  { my_aes_128_cbc, MY_AES_CBC, 128, MY_AES_BLOCK_SIZE, 3},
> +  { my_aes_192_cbc, MY_AES_CBC, 192, MY_AES_BLOCK_SIZE, 3},
> +  { my_aes_256_cbc, MY_AES_CBC, 256, MY_AES_BLOCK_SIZE, 3},
> +#ifdef HAVE_EncryptAes128Ctr
> +  { my_aes_128_ctr, MY_AES_CTR, 128, MY_AES_BLOCK_SIZE, 3},
> +  { my_aes_192_ctr, MY_AES_CTR, 192, MY_AES_BLOCK_SIZE, 3},
> +  { my_aes_256_ctr, MY_AES_CTR, 256, MY_AES_BLOCK_SIZE, 3},
> +#endif
> +#ifdef HAVE_EncryptAes128Gcm
> +  { my_aes_128_gcm, MY_AES_GCM, 128, 12, 4 },
> +  { my_aes_192_gcm, MY_AES_GCM, 192, 12, 4 },
> +  { my_aes_256_gcm, MY_AES_GCM, 256, 12, 4 },
> +#endif
> +};
> +
> +TYPELIB block_encryption_mode_typelib=
> +  { my_aes_block_encryption_end,
> +    "block_encryption_modes",
> +    my_aes_block_encryption_mode_str, NULL};
> +

I don't think all that belongs to my_crypt.cc and my_crypt.h.
This is part of the SQL interface to aes, it should be in item_strfunc.cc

>  extern "C" {
>  
>  int my_aes_crypt_init(void *ctx, enum my_aes_mode mode, int flags,
> diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc
> index acd3d74..9a4108c 100644
> --- a/sql/item_strfunc.cc
> +++ b/sql/item_strfunc.cc
> @@ -359,47 +359,116 @@ void Item_func_sha2::fix_length_and_dec()
>  }
>  
>  /* Implementation of AES encryption routines */
> -void Item_aes_crypt::create_key(String *user_key, uchar *real_key)
> +void Item_aes_crypt::create_item(String *user_item, uchar *real_item, uint 
> item_size)
>  {
> -  uchar *real_key_end= real_key + AES_KEY_LENGTH / 8;
> +  uchar *real_item_end= real_item + item_size;
>    uchar *ptr;
> -  const char *sptr= user_key->ptr();
> -  const char *key_end= sptr + user_key->length();
> +  const char *sptr= user_item->ptr();
> +  const char *item_end= sptr + user_item->length();
>  
> -  bzero(real_key, AES_KEY_LENGTH / 8);
> +  bzero(real_item, item_size);
>  
> -  for (ptr= real_key; sptr < key_end; ptr++, sptr++)
> +  for (ptr= real_item; sptr < item_end; ptr++, sptr++)
>    {
> -    if (ptr == real_key_end)
> -      ptr= real_key;
> +    if (ptr == real_item_end)
> +      return;
>      *ptr ^= (uchar) *sptr;
>    }
>  }
>  
> -
>  String *Item_aes_crypt::val_str(String *str)
>  {
>    DBUG_ASSERT(fixed == 1);
> -  StringBuffer<80> user_key_buf;
> +  StringBuffer<80> user_buf;
>    String *sptr= args[0]->val_str(str);
> -  String *user_key=  args[1]->val_str(&user_key_buf);
> -  uint32 aes_length;
> +  enum my_aes_mode block_cipher;
> +
> +  if (block_encryption_mode == (ulong)-1)
> +    block_encryption_mode= current_thd->variables.block_encryption_mode;
> +
> +  block_cipher= my_aes_info[block_encryption_mode].aes_mode;
> +
> +  if (arg_count > my_aes_info[block_encryption_mode].param_count)
> +  {
> +    null_value= 1;
> +    my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), what ? "AES_ENCRYPT" 
> : "AES_DECRYPT");
> +    return 0;
> +  }
>  
> -  if (sptr && user_key) // we need both arguments to be not NULL
> +  if (sptr)
>    {
> +    uchar riv[128];  // MY_AES_BLOCK_SIZE + MAX_AADLEN (=720 bits)
> +    uint32 digest_size; // size of digest in bytes
> +    String *user_iv;
> +    uint iv_size;
> +
> +
> +    digest_size= my_aes_get_size(block_cipher, sptr->length());
> +    iv_size= my_aes_info[block_encryption_mode].iv_size;
> +
> +    if (iv_size) // check if block encryption mode requires IV
> +    {

I thought you can have an assert here: DBUG_ASSERT(arg_count > 2)
because you do ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT earlier.

> +      if (arg_count > 2)
> +      {
> +        user_iv= args[2]->val_str(&user_buf);
> +        // throw error if iv is too short
> +        if (!user_iv || user_iv->length() < iv_size)
> +        {
> +          null_value= 1;
> +          my_error(ER_AES_INVALID_IV, MYF(0), iv_size);

This needs to be documented carefully, because it's inconsistent
with the behavior for key argument.

> +          return 0;
> +        }
> +        create_item(user_iv, riv, iv_size);
> +      } else {
> +        null_value= 1;
> +        my_error(ER_AES_INVALID_IV, MYF(0), iv_size);
> +        return 0;
> +      }
> +    }
> +#ifdef HAVE_EncryptAes128Gcm
> +    if (arg_count > 3 &&
> +        block_cipher == MY_AES_GCM)

why do you check for MY_AES_GCM, isn't arg_count > 3 enough?

> +    {
> +      String *user_aad= args[3]->val_str(&user_buf);
> +      if (user_aad)
> +      {
> +        create_item(user_aad, riv + iv_size, user_aad->length());
> +        iv_size+= user_aad->length();
> +      }
> +    }
> +#endif
>      null_value=0;
> -    aes_length=my_aes_get_size(MY_AES_ECB, sptr->length());
>  
> -    if (!str_value.alloc(aes_length))                // Ensure that memory 
> is free
> +    if (!str_value.alloc(digest_size))               // Ensure that memory 
> is free
>      {
> -      uchar rkey[AES_KEY_LENGTH / 8];
> -      create_key(user_key, rkey);
> +      String *user_key=  args[1]->val_str(&user_buf);
> +      uint32 key_size= my_aes_info[block_encryption_mode].key_size;
> +      uchar rkey[MY_AES_MAX_KEY_LENGTH];
>  
> -      if (!my_aes_crypt(MY_AES_ECB, what, (uchar*)sptr->ptr(), 
> sptr->length(),
> -                 (uchar*)str_value.ptr(), &aes_length,
> -                 rkey, AES_KEY_LENGTH / 8, 0, 0))
> +      if (!user_key || !user_key->length())
>        {
> -        str_value.length((uint) aes_length);
> +        null_value= 1;
> +        return 0;
> +      }

better to check the key before allocating any memory,
just as the old code did.

> +
> +      // if key is too short, throw a warning
> +      if (user_key->length() < (key_size / 8))
> +      {
> +        push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN,
> +                            ER_WARN_AES_KEY_TOO_SHORT,
> +                            ER_THD(current_thd, ER_WARN_AES_KEY_TOO_SHORT),
> +                            key_size / 8);
> +      }
> +
> +      create_item(user_key, rkey, key_size / 8);
> +
> +      if (!my_aes_crypt(block_cipher,
> +                  what, (uchar*)sptr->ptr(), sptr->length(),
> +                  (uchar*)str_value.ptr(), &digest_size,
> +                  rkey, key_size / 8, (arg_count > 2) ? riv : 0,
> +                  (arg_count > 2) ? iv_size : 0))
> +      {
> +        str_value.length((uint) digest_size);
>          return &str_value;
>        }
>      }
> diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h
> index 25b63eb..153ca7b 100644
> --- a/sql/item_strfunc.h
> +++ b/sql/item_strfunc.h
> @@ -184,8 +188,9 @@ class Item_func_aes_encrypt :public Item_aes_crypt
>  class Item_func_aes_decrypt :public Item_aes_crypt
>  {
>  public:
> -  Item_func_aes_decrypt(THD *thd, Item *a, Item *b):
> -    Item_aes_crypt(thd, a, b) {}
> +  Item_func_aes_decrypt(THD *thd, ulong mode, List<Item> &list_item):
> +    Item_aes_crypt(thd, list_item)
> +    { block_encryption_mode= mode; }

you also need to implement check_vcol_func_processor()
and check_valid_arguments_processor() to make sure that AES_ENCRYPT/AES_DECRYPT
without the USING clause are not used in partitioning expression and
in stored generated columns. And add tests for that, please.

after I push MDEV-5800 (or now in bb-10.2-vcols branch) see Item_func_week as
an example (it also has an optional argument that by default takes a value
from a session variable).

>    void fix_length_and_dec();
>    const char *func_name() const { return "aes_decrypt"; }
>    Item *get_copy(THD *thd, MEM_ROOT *mem_root)
> diff --git a/sql/mysqld.cc b/sql/mysqld.cc
> index 310ccb0..b2f677e 100644
> --- a/sql/mysqld.cc
> +++ b/sql/mysqld.cc
> @@ -8317,6 +8317,27 @@ static int show_memory_used(THD *thd, SHOW_VAR *var, 
> char *buff,
>    return 0;
>  }
>  
> +static int show_block_encrypt_mode_list(THD *thd, SHOW_VAR *var, char *buff,
> +                          struct system_status_var *status_var,
> +                            enum enum_var_type scope)
> +{
> +  uint i;
> +  char *end= buff + SHOW_VAR_FUNC_BUFF_SIZE;
> +  var->type= SHOW_CHAR;
> +  var->value= buff;
> +
> +  for (i=0; my_aes_block_encryption_mode_str[i] &&
> +            buff + strlen(my_aes_block_encryption_mode_str[i]) < end &&
> +            i < my_aes_block_encryption_end; i++)
> +  {
> +    buff= strnmov(buff, my_aes_block_encryption_mode_str[i], end-buff-1);
> +    *buff++= ',';
> +  }
> +  if (i)
> +    buff--;
> +  *buff= 0;
> +  return 0;
> +}

No need to, because block_encryption_mode_list status variable is not
needed either.

>  
>  #ifndef DBUG_OFF
>  static int debug_status_func(THD *thd, SHOW_VAR *var, char *buff,
> diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt
> index d42611b..5a4c8bf 100644
> --- a/sql/share/errmsg-utf8.txt
> +++ b/sql/share/errmsg-utf8.txt
> @@ -7231,4 +7231,16 @@ ER_PARTITION_DEFAULT_ERROR
>          eng "Only one DEFAULT partition allowed"
>          ukr "Припустимо мати тільки один DEFAULT розділ" 
>  ER_REFERENCED_TRG_DOES_NOT_EXIST
> -  eng "Referenced trigger '%s' for the given action time and event type does 
> not exist"
> +        eng "Referenced trigger '%s' for the given action time and event 
> type does not exist"
> +ER_AES_INVALID_IV
> +        eng "The initialization vector supplied is too short. Must be at 
> least %d bytes long"
> +        ger "Der angegebene Initalisierungsvektor ist zu kurz. Die 
> Mindestlänge beträgt %d Bytes"
> +ER_AES_INVALID_MODE
> +        eng "Unknown block encryption mode '%s'"
> +        ger "Der angegebene Blockcodierungsmodus '%s' exstiert nicht"
> +ER_AES_INVALID_KEY
> +        eng "Invalid key"
> +        ger "Ungültiger Schlüssel"

This error message seems to be unused.

> +ER_WARN_AES_KEY_TOO_SHORT
> +        eng "The provided key is too short. It is more secure to use a key 
> with %d bytes"

s/with %d bytes/of at least %d bytes long/

> +        ger "Der angegebene Schlüssel ist zu kurz. Es ist empfehlenswert 
> einen Schlüssel mit %d Bytes zu verwenden"
> diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
> index e17a514..12e79d3 100644
> --- a/sql/sql_yacc.yy
> +++ b/sql/sql_yacc.yy
> @@ -6670,7 +6673,6 @@ charset:
>            CHAR_SYM SET {}
>          | CHARSET {}
>          ;
> -

Huh?

>  charset_name:
>            ident_or_text
>            {
> @@ -7102,6 +7104,16 @@ opt_component:
>          | '.' ident      { $$= $2; }
>          ;
>  
> +block_encryption_mode:
> +          ident_or_text
> +          {
> +            if (!($$=find_type($1.str, &block_encryption_mode_typelib, 
> MYF(0))))
> +              my_yyabort_error((ER_AES_INVALID_MODE, MYF(0), $1.str));
> +            $$-= 1;
> +          }
> +        | DEFAULT { $$= (ulong)-1;}

why do you want to support USING DEFAULT ?

> +        ;
> +
>  string_list:
>            text_string
>            { Lex->last_field->interval_list.push_back($1, thd->mem_root); }
> diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc
> index b6359ff..8b39462 100644
> --- a/sql/sys_vars.cc
> +++ b/sql/sys_vars.cc
> @@ -554,6 +554,10 @@ static Sys_var_mybool 
> Sys_explicit_defaults_for_timestamp(
>         READ_ONLY GLOBAL_VAR(opt_explicit_defaults_for_timestamp),
>         CMD_LINE(OPT_ARG), DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG);
>  
> +static Sys_var_enum Sys_block_encryption_mode(
> +       "block_encryption_mode", "mode for AES_ENCRYPT/AES_DECRYPT",

"for AES_ENCRYPT and AES_DECRYPT"

> +       SESSION_VAR(block_encryption_mode), CMD_LINE(REQUIRED_ARG),
> +       my_aes_block_encryption_mode_str, DEFAULT(my_aes_128_ecb));
>  
>  static Sys_var_ulonglong Sys_bulk_insert_buff_size(
>         "bulk_insert_buffer_size", "Size of tree cache used in bulk "
> diff --git a/mysql-test/r/aes_cbc_crypt.result 
> b/mysql-test/r/aes_cbc_crypt.result
> new file mode 100644
> index 0000000..87ed84b
> --- /dev/null
> +++ b/mysql-test/r/aes_cbc_crypt.result
> @@ -0,0 +1,19300 @@
> +#
> +# Error checking
> +#
> +SELECT HEX(AES_ENCRYPT("Die Katze tritt die Treppe krumm", "foo", 
> "1234567890123456" USING "aes-128-cbc"));

I think it's better to use underscores aes_128_cbc, then the mode can be used as
an identifier:

   AES_ENCRYPT("Die Katze", "foo", "1234567890123456" USING aes_128_cbc);

(like charset names) otherwise it's not at all intuitive that

   SELECT AES_ENCRYPT("Die Katze", "foo", "1234567890123456"
                      USING "aes-128-cbc");

works, but

   SELECT AES_ENCRYPT("Die Katze", "foo", "1234567890123456"
                      USING concat_ws("-", "aes", "128", "cbc"));

doesn't. So, either exclude the possibility of this confusion by not
allowing a string literal as a mode. Or (actually better) allow any
arbitrary non-constant expression as the mode.

> +#
> +# Session variable tests
> +#
> +SET session block_encryption_mode="aes-111-cbc";
> +ERROR 42000: Variable 'block_encryption_mode' can't be set to the value of 
> 'aes-111-cbc'
> +SET session block_encryption_mode="aes-128-cbc";
> +SELECT LEFT(AES_ENCRYPT(X'53696E676C6520626C6F636B206D7367', 
> X'06A9214036B8A15B512E03D534120006', X'3DAFBA429D9EB430B422DA802C9FAC41'), 
> 16) = X'E353779C1079AEB82708942DBE77181A';
> +LEFT(AES_ENCRYPT(X'53696E676C6520626C6F636B206D7367', 
> X'06A9214036B8A15B512E03D534120006', X'3DAFBA429D9EB430B422DA802C9FAC41'), 
> 16) = X'E353779C1079AEB82708942DBE77181A'
> +1
> +SELECT AES_DECRYPT(AES_ENCRYPT(X'53696E676C6520626C6F636B206D7367', 
> X'06A9214036B8A15B512E03D534120006', X'3DAFBA429D9EB430B422DA802C9FAC41'), 
> X'06A9214036B8A15B512E03D534120006', X'3DAFBA429D9EB430B422DA802C9FAC41') = 
> X'53696E676C6520626C6F636B206D7367';
> +AES_DECRYPT(AES_ENCRYPT(X'53696E676C6520626C6F636B206D7367', 
> X'06A9214036B8A15B512E03D534120006', X'3DAFBA429D9EB430B422DA802C9FAC41'), 
> X'06A9214036B8A15B512E03D534120006', X'3DAFBA429D9EB430B422DA802C9FAC41') = 
> X'53696E676C6520626C6F636B206D7367'

please add tests for views, default clause, virtual columns, partitioning.

> +1
>  ...
I don't think it's necessary to commit megabytes of CAVP tests into the
server repository. May be just a few K of them will be enough?

Regards,
Sergei
Chief Architect MariaDB
and [email protected]

_______________________________________________
Mailing list: https://launchpad.net/~maria-developers
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~maria-developers
More help   : https://help.launchpad.net/ListHelp

Reply via email to