1 | /* |
2 | Copyright (c) 2015, MariaDB |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of the GNU General Public License as published by |
6 | the Free Software Foundation; version 2 of the License. |
7 | |
8 | This program is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | GNU General Public License for more details. |
12 | |
13 | You should have received a copy of the GNU General Public License |
14 | along with this program; if not, write to the Free Software |
15 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
16 | |
17 | /************************************************************************* |
18 | Limitation of encrypted IO_CACHEs |
19 | 1. Designed to support temporary files only (open_cached_file, fd=-1) |
20 | 2. Created with WRITE_CACHE, later can be reinit_io_cache'ed to |
21 | READ_CACHE and WRITE_CACHE in any order arbitrary number of times. |
22 | 3. no seeks for writes, but reinit_io_cache(WRITE_CACHE, seek_offset) |
23 | is allowed (there's a special hack in reinit_io_cache() for that) |
24 | */ |
25 | |
26 | #include "../mysys/mysys_priv.h" |
27 | #include "log.h" |
28 | #include "mysqld.h" |
29 | #include "sql_class.h" |
30 | |
31 | static uint keyid, keyver; |
32 | |
33 | #define set_iv(IV, N1, N2) \ |
34 | do { \ |
35 | compile_time_assert(sizeof(IV) >= sizeof(N1) + sizeof(N2)); \ |
36 | memcpy(IV, &(N1), sizeof(N1)); \ |
37 | memcpy(IV + sizeof(N1), &(N2), sizeof(N2)); \ |
38 | } while(0) |
39 | |
40 | static int my_b_encr_read(IO_CACHE *info, uchar *Buffer, size_t Count) |
41 | { |
42 | my_off_t pos_in_file= info->pos_in_file + (info->read_end - info->buffer); |
43 | my_off_t old_pos_in_file= pos_in_file, pos_offset= 0; |
44 | IO_CACHE_CRYPT *crypt_data= |
45 | (IO_CACHE_CRYPT *)(info->buffer + info->buffer_length + MY_AES_BLOCK_SIZE); |
46 | uchar *wbuffer= (uchar*)&(crypt_data->inbuf_counter); |
47 | uchar *ebuffer= (uchar*)(crypt_data + 1); |
48 | DBUG_ENTER("my_b_encr_read" ); |
49 | |
50 | if (pos_in_file == info->end_of_file) |
51 | { |
52 | info->read_pos= info->read_end= info->buffer; |
53 | info->pos_in_file= pos_in_file; |
54 | info->error= 0; |
55 | DBUG_RETURN(MY_TEST(Count)); |
56 | } |
57 | |
58 | if (info->seek_not_done) |
59 | { |
60 | my_off_t wpos; |
61 | |
62 | pos_offset= pos_in_file % info->buffer_length; |
63 | pos_in_file-= pos_offset; |
64 | |
65 | wpos= pos_in_file / info->buffer_length * crypt_data->block_length; |
66 | |
67 | if ((mysql_file_seek(info->file, wpos, MY_SEEK_SET, MYF(0)) |
68 | == MY_FILEPOS_ERROR)) |
69 | { |
70 | info->error= -1; |
71 | DBUG_RETURN(1); |
72 | } |
73 | info->seek_not_done= 0; |
74 | } |
75 | |
76 | do |
77 | { |
78 | size_t copied; |
79 | uint elength, wlength, length; |
80 | uchar iv[MY_AES_BLOCK_SIZE]= {0}; |
81 | |
82 | DBUG_ASSERT(pos_in_file % info->buffer_length == 0); |
83 | |
84 | if (info->end_of_file - pos_in_file >= info->buffer_length) |
85 | wlength= crypt_data->block_length; |
86 | else |
87 | wlength= crypt_data->last_block_length; |
88 | |
89 | if (mysql_file_read(info->file, wbuffer, wlength, info->myflags | MY_NABP)) |
90 | { |
91 | info->error= -1; |
92 | DBUG_RETURN(1); |
93 | } |
94 | |
95 | elength= wlength - (uint)(ebuffer - wbuffer); |
96 | set_iv(iv, pos_in_file, crypt_data->inbuf_counter); |
97 | |
98 | if (encryption_crypt(ebuffer, elength, info->buffer, &length, |
99 | crypt_data->key, sizeof(crypt_data->key), |
100 | iv, sizeof(iv), ENCRYPTION_FLAG_DECRYPT, |
101 | keyid, keyver)) |
102 | { |
103 | my_errno= 1; |
104 | DBUG_RETURN(info->error= -1); |
105 | } |
106 | |
107 | DBUG_ASSERT(length <= info->buffer_length); |
108 | |
109 | copied= MY_MIN(Count, (size_t)(length - pos_offset)); |
110 | |
111 | memcpy(Buffer, info->buffer + pos_offset, copied); |
112 | Count-= copied; |
113 | Buffer+= copied; |
114 | |
115 | info->read_pos= info->buffer + pos_offset + copied; |
116 | info->read_end= info->buffer + length; |
117 | info->pos_in_file= pos_in_file; |
118 | pos_in_file+= length; |
119 | pos_offset= 0; |
120 | |
121 | if (wlength < crypt_data->block_length && pos_in_file < info->end_of_file) |
122 | { |
123 | info->error= (int)(pos_in_file - old_pos_in_file); |
124 | DBUG_RETURN(1); |
125 | } |
126 | } while (Count); |
127 | |
128 | DBUG_RETURN(0); |
129 | } |
130 | |
131 | static int my_b_encr_write(IO_CACHE *info, const uchar *Buffer, size_t Count) |
132 | { |
133 | IO_CACHE_CRYPT *crypt_data= |
134 | (IO_CACHE_CRYPT *)(info->buffer + info->buffer_length + MY_AES_BLOCK_SIZE); |
135 | uchar *wbuffer= (uchar*)&(crypt_data->inbuf_counter); |
136 | uchar *ebuffer= (uchar*)(crypt_data + 1); |
137 | DBUG_ENTER("my_b_encr_write" ); |
138 | |
139 | if (Buffer != info->write_buffer) |
140 | { |
141 | Count-= Count % info->buffer_length; |
142 | if (!Count) |
143 | DBUG_RETURN(0); |
144 | } |
145 | |
146 | if (info->seek_not_done) |
147 | { |
148 | DBUG_ASSERT(info->pos_in_file % info->buffer_length == 0); |
149 | my_off_t wpos= info->pos_in_file / info->buffer_length * crypt_data->block_length; |
150 | |
151 | if ((mysql_file_seek(info->file, wpos, MY_SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR)) |
152 | { |
153 | info->error= -1; |
154 | DBUG_RETURN(1); |
155 | } |
156 | info->seek_not_done= 0; |
157 | } |
158 | |
159 | if (info->pos_in_file == 0) |
160 | { |
161 | if (my_random_bytes(crypt_data->key, sizeof(crypt_data->key))) |
162 | { |
163 | my_errno= 1; |
164 | DBUG_RETURN(info->error= -1); |
165 | } |
166 | crypt_data->counter= 0; |
167 | |
168 | IF_DBUG(crypt_data->block_length= 0,); |
169 | } |
170 | |
171 | do |
172 | { |
173 | size_t length= MY_MIN(info->buffer_length, Count); |
174 | uint elength, wlength; |
175 | uchar iv[MY_AES_BLOCK_SIZE]= {0}; |
176 | |
177 | crypt_data->inbuf_counter= crypt_data->counter; |
178 | set_iv(iv, info->pos_in_file, crypt_data->inbuf_counter); |
179 | |
180 | if (encryption_crypt(Buffer, (uint)length, ebuffer, &elength, |
181 | crypt_data->key, (uint) sizeof(crypt_data->key), |
182 | iv, (uint) sizeof(iv), ENCRYPTION_FLAG_ENCRYPT, |
183 | keyid, keyver)) |
184 | { |
185 | my_errno= 1; |
186 | DBUG_RETURN(info->error= -1); |
187 | } |
188 | wlength= elength + (uint)(ebuffer - wbuffer); |
189 | |
190 | if (length == info->buffer_length) |
191 | { |
192 | /* |
193 | block_length should be always the same. that is, encrypting |
194 | buffer_length bytes should *always* produce block_length bytes |
195 | */ |
196 | DBUG_ASSERT(crypt_data->block_length == 0 || crypt_data->block_length == wlength); |
197 | DBUG_ASSERT(elength <= encryption_encrypted_length((uint)length, keyid, keyver)); |
198 | crypt_data->block_length= wlength; |
199 | } |
200 | else |
201 | { |
202 | /* if we write a partial block, it *must* be the last write */ |
203 | IF_DBUG(info->write_function= 0,); |
204 | crypt_data->last_block_length= wlength; |
205 | } |
206 | |
207 | if (mysql_file_write(info->file, wbuffer, wlength, info->myflags | MY_NABP)) |
208 | DBUG_RETURN(info->error= -1); |
209 | |
210 | Buffer+= length; |
211 | Count-= length; |
212 | info->pos_in_file+= length; |
213 | crypt_data->counter++; |
214 | } while (Count); |
215 | DBUG_RETURN(0); |
216 | } |
217 | |
218 | /** |
219 | determine what key id and key version to use for IO_CACHE temp files |
220 | |
221 | First, try key id 2, if it doesn't exist, use key id 1. |
222 | |
223 | (key id 1 is the default system key id, used pretty much everywhere, it must |
224 | exist. key id 2 is for tempfiles, it can be used, for example, to set a |
225 | faster encryption algorithm for temporary files) |
226 | |
227 | This looks like it might have a bug: if an encryption plugin is unloaded when |
228 | there's an open IO_CACHE, that IO_CACHE will become unreadable after reinit. |
229 | But in fact it is safe, as an encryption plugin can only be unloaded on |
230 | server shutdown. |
231 | |
232 | Note that encrypt_tmp_files variable is read-only. |
233 | */ |
234 | int init_io_cache_encryption() |
235 | { |
236 | if (encrypt_tmp_files) |
237 | { |
238 | keyid= ENCRYPTION_KEY_TEMPORARY_DATA; |
239 | keyver= encryption_key_get_latest_version(keyid); |
240 | if (keyver == ENCRYPTION_KEY_VERSION_INVALID) |
241 | { |
242 | keyid= ENCRYPTION_KEY_SYSTEM_DATA; |
243 | keyver= encryption_key_get_latest_version(keyid); |
244 | } |
245 | if (keyver == ENCRYPTION_KEY_VERSION_INVALID) |
246 | { |
247 | sql_print_error("Failed to enable encryption of temporary files" ); |
248 | return 1; |
249 | } |
250 | |
251 | if (keyver != ENCRYPTION_KEY_NOT_ENCRYPTED) |
252 | { |
253 | sql_print_information("Using encryption key id %d for temporary files" , keyid); |
254 | _my_b_encr_read= my_b_encr_read; |
255 | _my_b_encr_write= my_b_encr_write; |
256 | return 0; |
257 | } |
258 | } |
259 | |
260 | _my_b_encr_read= 0; |
261 | _my_b_encr_write= 0; |
262 | return 0; |
263 | } |
264 | |
265 | |