summaryrefslogtreecommitdiffstats
path: root/mysys_ssl/my_crypt.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--mysys_ssl/my_crypt.cc369
1 files changed, 369 insertions, 0 deletions
diff --git a/mysys_ssl/my_crypt.cc b/mysys_ssl/my_crypt.cc
new file mode 100644
index 00000000..00447e73
--- /dev/null
+++ b/mysys_ssl/my_crypt.cc
@@ -0,0 +1,369 @@
+/*
+ Copyright (c) 2014 Google Inc.
+ Copyright (c) 2014, 2019, MariaDB Corporation.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+#include <my_global.h>
+#include <string.h>
+
+#define template _template /* bug in WolfSSL 4.4.0, see also violite.h */
+#include <openssl/evp.h>
+#undef template
+#include <openssl/aes.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+#include <my_crypt.h>
+#include <ssl_compat.h>
+#include <cstdint>
+
+#define CTX_ALIGN 16
+
+class MyCTX
+{
+public:
+ char ctx_buf[EVP_CIPHER_CTX_SIZE + CTX_ALIGN];
+ EVP_CIPHER_CTX* ctx;
+ MyCTX()
+ {
+#if CTX_ALIGN > 0
+ uintptr_t p= ((uintptr_t)ctx_buf + (CTX_ALIGN - 1)) & ~(CTX_ALIGN - 1);
+ ctx = reinterpret_cast<EVP_CIPHER_CTX*>(p);
+#else
+ ctx = (EVP_CIPHER_CTX*)ctx_buf;
+#endif
+
+ EVP_CIPHER_CTX_init(ctx);
+ }
+ virtual ~MyCTX()
+ {
+ EVP_CIPHER_CTX_reset(ctx);
+ ERR_remove_state(0);
+ }
+
+ virtual int init(const EVP_CIPHER *cipher, int encrypt, const uchar *key,
+ uint klen, const uchar *iv, uint ivlen)
+ {
+ compile_time_assert(MY_AES_CTX_SIZE >= sizeof(MyCTX));
+ if (unlikely(!cipher))
+ return MY_AES_BAD_KEYSIZE;
+
+ if (EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, encrypt) != 1)
+ return MY_AES_OPENSSL_ERROR;
+
+ DBUG_ASSERT(EVP_CIPHER_CTX_key_length(ctx) == (int)klen);
+ DBUG_ASSERT(EVP_CIPHER_CTX_iv_length(ctx) <= (int)ivlen);
+
+ return MY_AES_OK;
+ }
+ virtual int update(const uchar *src, uint slen, uchar *dst, uint *dlen)
+ {
+#ifdef HAVE_WOLFSSL
+ // WolfSSL checks parameters and does not like NULL pointers to be passed to function below.
+ if (!src)
+ {
+ static uchar dummy[MY_AES_BLOCK_SIZE];
+ DBUG_ASSERT(!slen);
+ src=dummy;
+ }
+#endif
+
+ if (EVP_CipherUpdate(ctx, dst, (int*)dlen, src, slen) != 1)
+ return MY_AES_OPENSSL_ERROR;
+ return MY_AES_OK;
+ }
+ virtual int finish(uchar *dst, uint *dlen)
+ {
+ if (EVP_CipherFinal_ex(ctx, dst, (int*)dlen) != 1)
+ return MY_AES_BAD_DATA;
+ return MY_AES_OK;
+ }
+};
+
+class MyCTX_nopad : public MyCTX
+{
+public:
+ const uchar *key;
+ uint klen, source_tail_len;
+ uchar oiv[MY_AES_BLOCK_SIZE];
+ uchar source_tail[MY_AES_BLOCK_SIZE];
+
+ MyCTX_nopad() : MyCTX() { }
+ ~MyCTX_nopad() = default;
+
+ int init(const EVP_CIPHER *cipher, int encrypt, const uchar *key, uint klen,
+ const uchar *iv, uint ivlen)
+ {
+ compile_time_assert(MY_AES_CTX_SIZE >= sizeof(MyCTX_nopad));
+ this->key= key;
+ this->klen= klen;
+ this->source_tail_len= 0;
+ if (ivlen)
+ memcpy(oiv, iv, ivlen);
+ DBUG_ASSERT(ivlen == 0 || ivlen == sizeof(oiv));
+
+ int res= MyCTX::init(cipher, encrypt, key, klen, iv, ivlen);
+
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+ return res;
+ }
+
+ /** Update last partial source block, stored in source_tail array. */
+ void update_source_tail(const uchar* src, uint slen)
+ {
+ if (!slen)
+ return;
+ uint new_tail_len= (source_tail_len + slen) % MY_AES_BLOCK_SIZE;
+ if (new_tail_len)
+ {
+ if (slen + source_tail_len < MY_AES_BLOCK_SIZE)
+ {
+ memcpy(source_tail + source_tail_len, src, slen);
+ }
+ else
+ {
+ DBUG_ASSERT(slen > new_tail_len);
+ memcpy(source_tail, src + slen - new_tail_len, new_tail_len);
+ }
+ }
+ source_tail_len= new_tail_len;
+ }
+
+ int update(const uchar *src, uint slen, uchar *dst, uint *dlen)
+ {
+ update_source_tail(src, slen);
+ return MyCTX::update(src, slen, dst, dlen);
+ }
+
+ int finish(uchar *dst, uint *dlen)
+ {
+ if (source_tail_len)
+ {
+ /*
+ Not much we can do, block ciphers cannot encrypt data that aren't
+ a multiple of the block length. At least not without padding.
+ Let's do something CTR-like for the last partial block.
+ */
+ uchar mask[MY_AES_BLOCK_SIZE];
+ uint mlen;
+
+ int rc= my_aes_crypt(MY_AES_ECB, ENCRYPTION_FLAG_ENCRYPT | ENCRYPTION_FLAG_NOPAD,
+ oiv, sizeof(mask), mask, &mlen, key, klen, 0, 0);
+ DBUG_ASSERT(rc == MY_AES_OK);
+ if (rc)
+ return rc;
+ DBUG_ASSERT(mlen == sizeof(mask));
+
+ for (uint i=0; i < source_tail_len; i++)
+ dst[i]= source_tail[i] ^ mask[i];
+ }
+ *dlen= source_tail_len;
+ return MY_AES_OK;
+ }
+};
+
+#define make_aes_dispatcher(mode) \
+ static inline const EVP_CIPHER *aes_ ## mode(uint klen) \
+ { \
+ switch (klen) { \
+ case 16: return EVP_aes_128_ ## mode(); \
+ case 24: return EVP_aes_192_ ## mode(); \
+ case 32: return EVP_aes_256_ ## mode(); \
+ default: return 0; \
+ } \
+ }
+
+make_aes_dispatcher(ecb)
+make_aes_dispatcher(cbc)
+#ifdef HAVE_EncryptAes128Ctr
+make_aes_dispatcher(ctr)
+#endif /* HAVE_EncryptAes128Ctr */
+#ifdef HAVE_EncryptAes128Gcm
+make_aes_dispatcher(gcm)
+
+/*
+ special implementation for GCM; to fit OpenSSL AES-GCM into the
+ existing my_aes_* API it does the following:
+ - IV tail (over 12 bytes) goes to AAD
+ - the tag is appended to the ciphertext
+*/
+
+class MyCTX_gcm : public MyCTX
+{
+public:
+ const uchar *aad;
+ int aadlen;
+ MyCTX_gcm() : MyCTX() { }
+ ~MyCTX_gcm() { }
+
+ int init(const EVP_CIPHER *cipher, int encrypt, const uchar *key, uint klen,
+ const uchar *iv, uint ivlen)
+ {
+ compile_time_assert(MY_AES_CTX_SIZE >= sizeof(MyCTX_gcm));
+ int res= MyCTX::init(cipher, encrypt, key, klen, iv, ivlen);
+ int real_ivlen= EVP_CIPHER_CTX_iv_length(ctx);
+ aad= iv + real_ivlen;
+ aadlen= ivlen - real_ivlen;
+ return res;
+ }
+
+ int update(const uchar *src, uint slen, uchar *dst, uint *dlen)
+ {
+ /*
+ note that this GCM class cannot do streaming decryption, because
+ it needs the tag (which is located at the end of encrypted data)
+ before decrypting the data. it can encrypt data piecewise, like, first
+ half, then the second half, but it must decrypt all at once
+ */
+ if (!EVP_CIPHER_CTX_encrypting(ctx))
+ {
+ /* encrypted string must contain authenticaton tag (see MDEV-11174) */
+ if (slen < MY_AES_BLOCK_SIZE)
+ return MY_AES_BAD_DATA;
+ slen-= MY_AES_BLOCK_SIZE;
+ if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, MY_AES_BLOCK_SIZE,
+ (void*)(src + slen)))
+ return MY_AES_OPENSSL_ERROR;
+ }
+ int unused;
+ if (aadlen && !EVP_CipherUpdate(ctx, NULL, &unused, aad, aadlen))
+ return MY_AES_OPENSSL_ERROR;
+ aadlen= 0;
+ return MyCTX::update(src, slen, dst, dlen);
+ }
+
+ int finish(uchar *dst, uint *dlen)
+ {
+ int fin;
+ if (!EVP_CipherFinal_ex(ctx, dst, &fin))
+ return MY_AES_BAD_DATA;
+ DBUG_ASSERT(fin == 0);
+
+ if (EVP_CIPHER_CTX_encrypting(ctx))
+ {
+ if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, MY_AES_BLOCK_SIZE, dst))
+ return MY_AES_OPENSSL_ERROR;
+ *dlen= MY_AES_BLOCK_SIZE;
+ }
+ else
+ *dlen= 0;
+ return MY_AES_OK;
+ }
+};
+
+#endif
+
+const EVP_CIPHER *(*ciphers[])(uint)= {
+ aes_ecb, aes_cbc
+#ifdef HAVE_EncryptAes128Ctr
+ , aes_ctr
+#ifdef HAVE_EncryptAes128Gcm
+ , aes_gcm
+#endif
+#endif
+};
+
+extern "C" {
+
+int my_aes_crypt_init(void *ctx, enum my_aes_mode mode, int flags,
+ const unsigned char* key, unsigned int klen,
+ const unsigned char* iv, unsigned int ivlen)
+{
+#ifdef HAVE_EncryptAes128Ctr
+#ifdef HAVE_EncryptAes128Gcm
+ if (mode == MY_AES_GCM)
+ if (flags & ENCRYPTION_FLAG_NOPAD)
+ return MY_AES_OPENSSL_ERROR;
+ else
+ new (ctx) MyCTX_gcm();
+ else
+#endif
+ if (mode == MY_AES_CTR)
+ new (ctx) MyCTX();
+ else
+#endif
+ if (flags & ENCRYPTION_FLAG_NOPAD)
+ new (ctx) MyCTX_nopad();
+ else
+ new (ctx) MyCTX();
+ return ((MyCTX*)ctx)->init(ciphers[mode](klen), flags & 1,
+ key, klen, iv, ivlen);
+}
+
+int my_aes_crypt_update(void *ctx, const uchar *src, uint slen,
+ uchar *dst, uint *dlen)
+{
+ return ((MyCTX*)ctx)->update(src, slen, dst, dlen);
+}
+
+int my_aes_crypt_finish(void *ctx, uchar *dst, uint *dlen)
+{
+ int res= ((MyCTX*)ctx)->finish(dst, dlen);
+ ((MyCTX*)ctx)->~MyCTX();
+ return res;
+}
+
+int my_aes_crypt(enum my_aes_mode mode, int flags,
+ const uchar *src, uint slen, uchar *dst, uint *dlen,
+ const uchar *key, uint klen, const uchar *iv, uint ivlen)
+{
+ void *ctx= alloca(MY_AES_CTX_SIZE);
+ int res1, res2;
+ uint d1= 0, d2;
+ if ((res1= my_aes_crypt_init(ctx, mode, flags, key, klen, iv, ivlen)))
+ return res1;
+ res1= my_aes_crypt_update(ctx, src, slen, dst, &d1);
+ res2= my_aes_crypt_finish(ctx, dst + d1, &d2);
+ if (res1 || res2)
+ ERR_remove_state(0); /* in case of failure clear error queue */
+ else
+ *dlen= d1 + d2;
+ return res1 ? res1 : res2;
+}
+
+
+/*
+ calculate the length of the cyphertext from the length of the plaintext
+ for different AES encryption modes with padding enabled.
+ Without padding (ENCRYPTION_FLAG_NOPAD) cyphertext has the same length
+ as the plaintext
+*/
+unsigned int my_aes_get_size(enum my_aes_mode mode __attribute__((unused)), unsigned int source_length)
+{
+#ifdef HAVE_EncryptAes128Ctr
+ if (mode == MY_AES_CTR)
+ return source_length;
+#ifdef HAVE_EncryptAes128Gcm
+ if (mode == MY_AES_GCM)
+ return source_length + MY_AES_BLOCK_SIZE;
+#endif
+#endif
+ return (source_length / MY_AES_BLOCK_SIZE + 1) * MY_AES_BLOCK_SIZE;
+}
+
+
+unsigned int my_aes_ctx_size(enum my_aes_mode)
+{
+ return MY_AES_CTX_SIZE;
+}
+
+int my_random_bytes(uchar *buf, int num)
+{
+ if (RAND_bytes(buf, num) != 1)
+ return MY_AES_OPENSSL_ERROR;
+ return MY_AES_OK;
+}
+
+}