log0crypt.cc 9.62 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*****************************************************************************

Copyright (C) 2013, 2015, Google Inc. All Rights Reserved.
Copyright (C) 2014, 2015, MariaDB Corporation. All Rights Reserved.

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-1301 USA

*****************************************************************************/
Monty's avatar
Monty committed
19 20 21 22
/**************************************************//**
@file log0crypt.cc
Innodb log encrypt/decrypt

23 24
Created 11/25/2013 Minli Zhu Google
Modified           Jan Lindström jan.lindstrom@mariadb.com
Monty's avatar
Monty committed
25 26 27 28
*******************************************************/
#include "m_string.h"
#include "log0crypt.h"
#include <my_crypt.h>
29
#include <my_aes.h>
Sergei Golubchik's avatar
Sergei Golubchik committed
30

Monty's avatar
Monty committed
31 32 33 34
#include "log0log.h"
#include "srv0start.h" // for srv_start_lsn
#include "log0recv.h"  // for recv_sys

35
#include "ha_prototypes.h" // IB_LOG_
36

Monty's avatar
Monty committed
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
/* If true, enable redo log encryption. */
UNIV_INTERN my_bool srv_encrypt_log = FALSE;
/*
  Sub system type for InnoDB redo log crypto.
  Set and used to validate crypto msg.
*/
static const byte redo_log_purpose_byte = 0x02;
/* Plain text used by AES_ECB to generate redo log crypt key. */
byte redo_log_crypt_msg[MY_AES_BLOCK_SIZE] = {0};
/* IV to concatenate with counter used by AES_CTR for redo log
 * encryption/decryption. */
byte aes_ctr_nonce[MY_AES_BLOCK_SIZE] = {0};

/*********************************************************************//**
Generate a 128-bit value used to generate crypt key for redo log.
It is generated via the concatenation of 1 purpose byte (0x02) and 15-byte
random number.
Init AES-CTR iv/nonce with random number.
It is called when:
- redo logs do not exist when start up, or
- transition from without crypto.
Note:
We should not use flags and conditions such as:
	(srv_encrypt_log &&
	 debug_use_static_keys &&
62
	 get_latest_encryption_key_version() == UNENCRYPTED_KEY_VER)
Monty's avatar
Monty committed
63 64 65 66 67 68 69 70 71 72 73
because they haven't been read and set yet in the situation of resetting
redo logs.
*/
UNIV_INTERN
void
log_init_crypt_msg_and_nonce(void)
/*==============================*/
{
	mach_write_to_1(redo_log_crypt_msg, redo_log_purpose_byte);
	if (my_random_bytes(redo_log_crypt_msg + 1, PURPOSE_BYTE_LEN) != AES_OK)
	{
74 75 76
		ib_logf(IB_LOG_LEVEL_ERROR,
			"Redo log crypto: generate "
			"%u-byte random number as crypto msg failed.",
Monty's avatar
Monty committed
77 78 79 80 81 82
			PURPOSE_BYTE_LEN);
		abort();
	}

	if (my_random_bytes(aes_ctr_nonce, MY_AES_BLOCK_SIZE) != AES_OK)
	{
83 84 85
		ib_logf(IB_LOG_LEVEL_ERROR,
			"Redo log crypto: generate "
			"%u-byte random number as AES_CTR nonce failed.",
Monty's avatar
Monty committed
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
			MY_AES_BLOCK_SIZE);
		abort();
	}
}

/*********************************************************************//**
Generate crypt key from crypt msg. */
UNIV_INTERN
void
log_init_crypt_key(
/*===============*/
	const byte* crypt_msg,		/*< in: crypt msg */
	const uint crypt_ver,		/*< in: key version */
	byte* key)			/*< out: crypt key*/
{
	if (crypt_ver == UNENCRYPTED_KEY_VER)
	{
103 104
		ib_logf(IB_LOG_LEVEL_INFO,
			"Redo log crypto: unencrypted key ver.");
Monty's avatar
Monty committed
105 106 107 108 109 110
		memset(key, 0, MY_AES_BLOCK_SIZE);
		return;
	}

	if (crypt_msg[PURPOSE_BYTE_OFFSET] != redo_log_purpose_byte)
	{
111 112 113
		ib_logf(IB_LOG_LEVEL_ERROR,
			"Redo log crypto: msg type mismatched. "
			"Expected: %x; Actual: %x.",
Monty's avatar
Monty committed
114 115 116 117 118
			redo_log_purpose_byte, crypt_msg[PURPOSE_BYTE_OFFSET]);
		abort();
	}

	byte mysqld_key[MY_AES_BLOCK_SIZE] = {0};
Sergei Golubchik's avatar
Sergei Golubchik committed
119 120
        uint keylen= sizeof(mysqld_key);
	if (get_encryption_key(crypt_ver, mysqld_key, &keylen))
Monty's avatar
Monty committed
121
	{
122 123 124
		ib_logf(IB_LOG_LEVEL_ERROR,
			"Redo log crypto: getting mysqld crypto key "
			"from key version failed.");
Monty's avatar
Monty committed
125 126 127 128
		abort();
	}

	uint32 dst_len;
129
	int rc= my_aes_encrypt_ecb(crypt_msg, MY_AES_BLOCK_SIZE, //src, srclen
Monty's avatar
Monty committed
130 131
                        key, &dst_len, //dst, &dstlen
                        (unsigned char*)&mysqld_key, sizeof(mysqld_key),
132
                        NULL, 0, 1);
Monty's avatar
Monty committed
133 134 135

	if (rc != AES_OK || dst_len != MY_AES_BLOCK_SIZE)
	{
136 137 138
		ib_logf(IB_LOG_LEVEL_ERROR,
			"Redo log crypto: getting redo log crypto key "
			"failed.");
Monty's avatar
Monty committed
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
		abort();
	}
}

/*********************************************************************//**
Get a log block's start lsn.
@return a log block's start lsn */
static inline
lsn_t
log_block_get_start_lsn(
/*====================*/
	lsn_t lsn,			/*!< in: checkpoint lsn */
	ulint log_block_no)		/*!< in: log block number */
{
	lsn_t start_lsn =
		(lsn & (lsn_t)0xffffffff00000000ULL) |
		(((log_block_no - 1) & (lsn_t)0x3fffffff) << 9);
	return start_lsn;
}

/*********************************************************************//**
Call AES CTR to encrypt/decrypt log blocks. */
static
Crypt_result
log_blocks_crypt(
/*=============*/
	const byte* block,		/*!< in: blocks before encrypt/decrypt*/
	const ulint size,		/*!< in: size of block, must be multiple of a log block*/
	byte* dst_block,		/*!< out: blocks after encrypt/decrypt */
	const bool is_encrypt)		/*!< in: encrypt or decrypt*/
{
	byte *log_block = (byte*)block;
	Crypt_result rc = AES_OK;
	uint32 src_len, dst_len;
	byte aes_ctr_counter[MY_AES_BLOCK_SIZE];
	ulint log_block_no, log_block_start_lsn;
	byte *key;
	ulint lsn;
	if (is_encrypt)
	{
		ut_a(log_sys && log_sys->redo_log_crypt_ver != UNENCRYPTED_KEY_VER);
		key = (byte *)(log_sys->redo_log_crypt_key);
		lsn = log_sys->lsn;

	} else {
		ut_a(recv_sys && recv_sys->recv_log_crypt_ver != UNENCRYPTED_KEY_VER);
		key = (byte *)(recv_sys->recv_log_crypt_key);
		lsn = srv_start_lsn;
	}
	ut_a(size % OS_FILE_LOG_BLOCK_SIZE == 0);
	src_len = OS_FILE_LOG_BLOCK_SIZE - LOG_BLOCK_HDR_SIZE;
	for (ulint i = 0; i < size ; i += OS_FILE_LOG_BLOCK_SIZE)
	{
		log_block_no = log_block_get_hdr_no(log_block);
		log_block_start_lsn = log_block_get_start_lsn(lsn, log_block_no);

		// Assume log block header is not encrypted
		memcpy(dst_block, log_block, LOG_BLOCK_HDR_SIZE);

		// aes_ctr_counter = nonce(3-byte) + start lsn to a log block
		// (8-byte) + lbn (4-byte) + abn
		// (1-byte, only 5 bits are used). "+" means concatenate.
		bzero(aes_ctr_counter, MY_AES_BLOCK_SIZE);
		memcpy(aes_ctr_counter, &aes_ctr_nonce, 3);
		mach_write_to_8(aes_ctr_counter + 3, log_block_start_lsn);
		mach_write_to_4(aes_ctr_counter + 11, log_block_no);
		bzero(aes_ctr_counter + 15, 1);

207 208 209 210 211
		int rc = encrypt_data(log_block + LOG_BLOCK_HDR_SIZE, src_len,
		                      dst_block + LOG_BLOCK_HDR_SIZE, &dst_len,
		                      (unsigned char*)key, 16,
		                      aes_ctr_counter, MY_AES_BLOCK_SIZE, 1,
                                      recv_sys->recv_log_crypt_ver);
Monty's avatar
Monty committed
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

		ut_a(rc == AES_OK);
		ut_a(dst_len == src_len);
		log_block += OS_FILE_LOG_BLOCK_SIZE;
		dst_block += OS_FILE_LOG_BLOCK_SIZE;
	}

	return rc;
}

/*********************************************************************//**
Encrypt log blocks. */
UNIV_INTERN
Crypt_result
log_blocks_encrypt(
/*===============*/
	const byte* block,		/*!< in: blocks before encryption */
	const ulint size,		/*!< in: size of blocks, must be multiple of a log block */
	byte* dst_block)		/*!< out: blocks after encryption */
{
	return log_blocks_crypt(block, size, dst_block, true);
}

/*********************************************************************//**
Decrypt log blocks. */
UNIV_INTERN
Crypt_result
log_blocks_decrypt(
/*===============*/
	const byte* block,		/*!< in: blocks before decryption */
	const ulint size,		/*!< in: size of blocks, must be multiple of a log block */
	byte* dst_block)		/*!< out: blocks after decryption */
{
	return log_blocks_crypt(block, size, dst_block, false);
}

/*********************************************************************//**
Set next checkpoint's key version to latest one, and generate current
key. Key version 0 means no encryption. */
UNIV_INTERN
void
log_crypt_set_ver_and_key(
/*======================*/
	uint& key_ver,			/*!< out: latest key version */
	byte* crypt_key)		/*!< out: crypto key */
{
258 259 260 261 262 263 264 265
	bool encrypted;

	if (srv_encrypt_log) {
		unsigned int vkey;
		vkey = get_latest_encryption_key_version();
		encrypted = true;

		if (vkey == UNENCRYPTED_KEY_VER ||
266
		    vkey == BAD_ENCRYPTION_KEY_VERSION) {
267 268
			encrypted = false;

269 270 271 272 273
			ib_logf(IB_LOG_LEVEL_WARN,
				"Redo log crypto: Can't initialize to key version %du.", vkey);
			ib_logf(IB_LOG_LEVEL_WARN,
				"Disabling redo log encryption.");

274 275 276 277 278 279 280 281 282
			srv_encrypt_log = FALSE;
		} else {
			key_ver = vkey;
		}
	} else {
		encrypted = false;
	}

	if (!encrypted) {
Monty's avatar
Monty committed
283 284 285 286
		key_ver = UNENCRYPTED_KEY_VER;
		memset(crypt_key, 0, MY_AES_BLOCK_SIZE);
		return;
	}
287

Monty's avatar
Monty committed
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
	log_init_crypt_key(redo_log_crypt_msg, key_ver, crypt_key);
}

/*********************************************************************//**
Writes the crypto (version, msg and iv) info, which has been used for
log blocks with lsn <= this checkpoint's lsn, to a log header's
checkpoint buf. */
UNIV_INTERN
void
log_crypt_write_checkpoint_buf(
/*===========================*/
	byte*	buf)			/*!< in/out: checkpoint buffer */
{
	ut_a(log_sys);
	mach_write_to_4(buf + LOG_CRYPT_VER, log_sys->redo_log_crypt_ver);
	if (!srv_encrypt_log ||
	    log_sys->redo_log_crypt_ver == UNENCRYPTED_KEY_VER) {
		memset(buf + LOG_CRYPT_MSG, 0, MY_AES_BLOCK_SIZE);
		memset(buf + LOG_CRYPT_IV, 0, MY_AES_BLOCK_SIZE);
		return;
	}
	ut_a(redo_log_crypt_msg[PURPOSE_BYTE_OFFSET] == redo_log_purpose_byte);
	memcpy(buf + LOG_CRYPT_MSG, redo_log_crypt_msg, MY_AES_BLOCK_SIZE);
	memcpy(buf + LOG_CRYPT_IV, aes_ctr_nonce, MY_AES_BLOCK_SIZE);
}