1 | /************************************************************************************ |
2 | Copyright (C) 2017 MariaDB Corporation AB |
3 | |
4 | This library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Library General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2 of the License, or (at your option) any later version. |
8 | |
9 | This library is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | Library General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU Library General Public |
15 | License along with this library; if not see <http://www.gnu.org/licenses> |
16 | or write to the Free Software Foundation, Inc., |
17 | 51 Franklin St., Fifth Floor, Boston, MA 02110, USA |
18 | *************************************************************************************/ |
19 | #ifndef _WIN32 |
20 | #define _GNU_SOURCE 1 |
21 | #endif |
22 | |
23 | #ifdef _WIN32 |
24 | #define HAVE_WINCRYPT |
25 | #undef HAVE_OPENSSL |
26 | #undef HAVE_GNUTLS |
27 | #pragma comment(lib, "crypt32.lib") |
28 | #pragma comment(lib, "ws2_32.lib") |
29 | #endif |
30 | |
31 | #if defined(HAVE_OPENSSL) || defined(HAVE_WINCRYPT) || defined(HAVE_GNUTLS) |
32 | |
33 | #include <ma_global.h> |
34 | #include <mysql.h> |
35 | #include <mysql/client_plugin.h> |
36 | #include <string.h> |
37 | #include <memory.h> |
38 | #include <errmsg.h> |
39 | #include <ma_global.h> |
40 | #include <ma_sys.h> |
41 | #include <ma_common.h> |
42 | |
43 | #ifndef WIN32 |
44 | #include <dlfcn.h> |
45 | #endif |
46 | |
47 | #if defined(HAVE_OPENSSL) |
48 | #include <openssl/rsa.h> |
49 | #include <openssl/pem.h> |
50 | #include <openssl/err.h> |
51 | #elif defined(HAVE_GNUTLS) |
52 | #include <gnutls/gnutls.h> |
53 | #elif defined(HAVE_WINCRYPT) |
54 | #include <windows.h> |
55 | #include <wincrypt.h> |
56 | #include <bcrypt.h> |
57 | #pragma comment(lib, "bcrypt.lib") |
58 | #pragma comment(lib, "crypt32.lib") |
59 | extern BCRYPT_ALG_HANDLE RsaProv; |
60 | extern BCRYPT_ALG_HANDLE Sha256Prov; |
61 | #endif |
62 | |
63 | #include <ma_crypt.h> |
64 | |
65 | #define MAX_PW_LEN 1024 |
66 | |
67 | #define REQUEST_PUBLIC_KEY 2 |
68 | #define CACHED_LOGIN_SUCCEEDED 3 |
69 | #define RSA_LOGIN_REQUIRED 4 |
70 | |
71 | /* MySQL server allows requesting public key only for non secure connections. |
72 | secure connections are: |
73 | - TLS/SSL connections |
74 | - unix_socket connections |
75 | */ |
76 | static unsigned char is_connection_secure(MYSQL *mysql) |
77 | { |
78 | if (mysql->options.use_ssl || |
79 | mysql->net.pvio->type != PVIO_TYPE_SOCKET) |
80 | return 1; |
81 | return 0; |
82 | } |
83 | |
84 | static int ma_sha256_scramble(unsigned char *scramble, size_t scramble_len, |
85 | unsigned char *source, size_t source_len, |
86 | unsigned char *salt, size_t salt_len) |
87 | { |
88 | unsigned char digest1[MA_SHA256_HASH_SIZE], |
89 | digest2[MA_SHA256_HASH_SIZE], |
90 | new_scramble[MA_SHA256_HASH_SIZE]; |
91 | #ifdef HAVE_WINCRYPT |
92 | MA_HASH_CTX myctx; |
93 | MA_HASH_CTX *ctx= &myctx; |
94 | #else |
95 | MA_HASH_CTX *ctx = NULL; |
96 | #endif |
97 | size_t i; |
98 | |
99 | /* check if all specified lenghts are valid */ |
100 | if (!scramble_len || !source_len || !salt_len) |
101 | return 1; |
102 | |
103 | |
104 | /* Step1: create sha256 from source */ |
105 | if (!(ctx= ma_hash_new(MA_HASH_SHA256, ctx))) |
106 | return 1; |
107 | ma_hash_input(ctx, source, source_len); |
108 | ma_hash_result(ctx, digest1); |
109 | ma_hash_free(ctx); |
110 | #ifndef HAVE_WINCRYPT |
111 | ctx = NULL; |
112 | #endif |
113 | |
114 | /* Step2: create sha256 digest from digest1 */ |
115 | if (!(ctx= ma_hash_new(MA_HASH_SHA256, ctx))) |
116 | return 1; |
117 | ma_hash_input(ctx, digest1, MA_SHA256_HASH_SIZE); |
118 | ma_hash_result(ctx, digest2); |
119 | ma_hash_free(ctx); |
120 | #ifndef HAVE_WINCRYPT |
121 | ctx = NULL; |
122 | #endif |
123 | |
124 | /* Step3: create sha256 digest from digest2 + salt */ |
125 | if (!(ctx= ma_hash_new(MA_HASH_SHA256, ctx))) |
126 | return 1; |
127 | ma_hash_input(ctx, digest2, MA_SHA256_HASH_SIZE); |
128 | ma_hash_input(ctx, salt, salt_len); |
129 | ma_hash_result(ctx, new_scramble); |
130 | ma_hash_free(ctx); |
131 | |
132 | /* Step4: xor(digest1, scramble1) */ |
133 | for (i= 0; i < scramble_len; i++) |
134 | scramble[i]= digest1[i] ^ new_scramble[i]; |
135 | return 0; |
136 | } |
137 | |
138 | /* function prototypes */ |
139 | static int auth_caching_sha2_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); |
140 | static int auth_caching_sha2_deinit(); |
141 | static int auth_caching_sha2_init(char *unused1, |
142 | size_t unused2, |
143 | int unused3, |
144 | va_list); |
145 | |
146 | |
147 | #ifndef PLUGIN_DYNAMIC |
148 | struct st_mysql_client_plugin_AUTHENTICATION caching_sha2_password_client_plugin= |
149 | #else |
150 | struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = |
151 | #endif |
152 | { |
153 | MYSQL_CLIENT_AUTHENTICATION_PLUGIN, |
154 | MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION, |
155 | "caching_sha2_password" , |
156 | "Georg Richter" , |
157 | "Caching SHA2 Authentication Plugin" , |
158 | {0,1,0}, |
159 | "LGPL" , |
160 | NULL, |
161 | auth_caching_sha2_init, |
162 | auth_caching_sha2_deinit, |
163 | NULL, |
164 | auth_caching_sha2_client |
165 | }; |
166 | |
167 | #ifdef HAVE_WINCRYPT |
168 | static LPBYTE ma_load_pem(const char *buffer, DWORD *buffer_len) |
169 | { |
170 | LPBYTE der_buffer= NULL; |
171 | DWORD der_buffer_length= 0; |
172 | |
173 | if (buffer_len == NULL || *buffer_len == 0) |
174 | return NULL; |
175 | /* calculate the length of DER binary */ |
176 | if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, |
177 | NULL, &der_buffer_length, NULL, NULL)) |
178 | goto end; |
179 | /* allocate DER binary buffer */ |
180 | if (!(der_buffer= (LPBYTE)malloc(der_buffer_length))) |
181 | goto end; |
182 | /* convert to DER binary */ |
183 | if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, |
184 | der_buffer, &der_buffer_length, NULL, NULL)) |
185 | goto end; |
186 | |
187 | *buffer_len= der_buffer_length; |
188 | |
189 | return der_buffer; |
190 | |
191 | end: |
192 | if (der_buffer) |
193 | free(der_buffer); |
194 | *buffer_len= 0; |
195 | return NULL; |
196 | } |
197 | #endif |
198 | |
199 | #ifndef HAVE_GNUTLS |
200 | static char *load_pub_key_file(const char *filename, int *pub_key_size) |
201 | { |
202 | FILE *fp= NULL; |
203 | char *buffer= NULL; |
204 | unsigned char error= 1; |
205 | |
206 | if (!pub_key_size) |
207 | return NULL; |
208 | |
209 | if (!(fp= fopen(filename, "r" ))) |
210 | goto end; |
211 | |
212 | if (fseek(fp, 0, SEEK_END)) |
213 | goto end; |
214 | |
215 | *pub_key_size= ftell(fp); |
216 | rewind(fp); |
217 | |
218 | if (!(buffer= malloc(*pub_key_size + 1))) |
219 | goto end; |
220 | |
221 | if (!fread(buffer, *pub_key_size, 1, fp)) |
222 | goto end; |
223 | |
224 | error= 0; |
225 | |
226 | end: |
227 | if (fp) |
228 | fclose(fp); |
229 | if (error && buffer) |
230 | { |
231 | free(buffer); |
232 | buffer= NULL; |
233 | } |
234 | return buffer; |
235 | } |
236 | #endif |
237 | |
238 | static int auth_caching_sha2_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) |
239 | { |
240 | unsigned char *packet; |
241 | int packet_length; |
242 | int rc= CR_ERROR; |
243 | #if !defined(HAVE_GNUTLS) |
244 | char passwd[MAX_PW_LEN]; |
245 | unsigned char rsa_enc_pw[MAX_PW_LEN]; |
246 | #ifdef HAVE_OPENSSL |
247 | int rsa_size; |
248 | #else |
249 | ULONG rsa_size; |
250 | #endif |
251 | unsigned int pwlen, i; |
252 | char *filebuffer= NULL; |
253 | #endif |
254 | unsigned char buf[MA_SHA256_HASH_SIZE]; |
255 | |
256 | #if defined(HAVE_OPENSSL) |
257 | RSA *pubkey= NULL; |
258 | BIO *bio; |
259 | #elif defined(HAVE_WINCRYPT) |
260 | BCRYPT_KEY_HANDLE pubkey= 0; |
261 | BCRYPT_OAEP_PADDING_INFO paddingInfo; |
262 | LPBYTE der_buffer= NULL; |
263 | DWORD der_buffer_len= 0; |
264 | CERT_PUBLIC_KEY_INFO *publicKeyInfo= NULL; |
265 | DWORD publicKeyInfoLen; |
266 | #endif |
267 | |
268 | /* read error */ |
269 | if ((packet_length= vio->read_packet(vio, &packet)) < 0) |
270 | return CR_ERROR; |
271 | |
272 | if (packet_length != SCRAMBLE_LENGTH + 1) |
273 | return CR_SERVER_HANDSHAKE_ERR; |
274 | |
275 | memmove(mysql->scramble_buff, packet, SCRAMBLE_LENGTH); |
276 | mysql->scramble_buff[SCRAMBLE_LENGTH]= 0; |
277 | |
278 | /* send empty packet if no password was provided */ |
279 | if (!mysql->passwd || !mysql->passwd[0]) |
280 | { |
281 | if (vio->write_packet(vio, 0, 0)) |
282 | return CR_ERROR; |
283 | return CR_OK; |
284 | } |
285 | |
286 | /* This is the normal authentication, if the host/user key is already in server |
287 | cache. In case authentication will fail, we will not return an error but will |
288 | try to connect via RSA encryption. |
289 | */ |
290 | if (ma_sha256_scramble(buf, MA_SHA256_HASH_SIZE, |
291 | (unsigned char *)mysql->passwd, strlen(mysql->passwd), |
292 | (unsigned char *)mysql->scramble_buff, SCRAMBLE_LENGTH)) |
293 | return CR_ERROR; |
294 | |
295 | if (vio->write_packet(vio, buf, MA_SHA256_HASH_SIZE)) |
296 | return CR_ERROR; |
297 | if ((packet_length=vio->read_packet(vio, &packet)) == -1) |
298 | return CR_ERROR; |
299 | if (packet_length == 1) |
300 | { |
301 | switch (*packet) { |
302 | case CACHED_LOGIN_SUCCEEDED: |
303 | return CR_OK; |
304 | case RSA_LOGIN_REQUIRED: |
305 | break; |
306 | default: |
307 | return CR_ERROR; |
308 | } |
309 | } |
310 | |
311 | if (!is_connection_secure(mysql)) |
312 | { |
313 | #if defined(HAVE_GNUTLS) |
314 | mysql->methods->set_error(mysql, CR_AUTH_PLUGIN_ERR, "HY000" , |
315 | "RSA Encrytion not supported - caching_sha2_password plugin was built with GnuTLS support" ); |
316 | return CR_ERROR; |
317 | #else |
318 | /* read public key file (if specified) */ |
319 | if (mysql->options.extension && |
320 | mysql->options.extension->server_public_key) |
321 | { |
322 | filebuffer= load_pub_key_file(mysql->options.extension->server_public_key, |
323 | &packet_length); |
324 | } |
325 | |
326 | /* if no public key file was specified or if we couldn't read the file, |
327 | we ask server to send public key */ |
328 | if (!filebuffer) |
329 | { |
330 | unsigned char request= REQUEST_PUBLIC_KEY; |
331 | if (vio->write_packet(vio, &request, 1) || |
332 | (packet_length=vio->read_packet(vio, &packet)) == -1) |
333 | { |
334 | mysql->methods->set_error(mysql, CR_AUTH_PLUGIN_ERR, "HY000" , "Couldn't read RSA public key from server" ); |
335 | return CR_ERROR; |
336 | } |
337 | } |
338 | #if defined(HAVE_OPENSSL) |
339 | bio= BIO_new_mem_buf(filebuffer ? (unsigned char *)filebuffer : packet, |
340 | packet_length); |
341 | if ((pubkey= PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL))) |
342 | rsa_size= RSA_size(pubkey); |
343 | BIO_free(bio); |
344 | ERR_clear_error(); |
345 | #elif defined(HAVE_WINCRYPT) |
346 | der_buffer_len= packet_length; |
347 | /* Load pem and convert it to binary object. New length will be returned |
348 | in der_buffer_len */ |
349 | if (!(der_buffer= ma_load_pem(filebuffer ? filebuffer : (char *)packet, &der_buffer_len))) |
350 | goto error; |
351 | |
352 | /* Create context and load public key */ |
353 | if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, |
354 | der_buffer, der_buffer_len, |
355 | CRYPT_DECODE_ALLOC_FLAG, NULL, |
356 | &publicKeyInfo, &publicKeyInfoLen)) |
357 | goto error; |
358 | free(der_buffer); |
359 | |
360 | /* Import public key as cng key */ |
361 | if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, publicKeyInfo, |
362 | CRYPT_OID_INFO_PUBKEY_ENCRYPT_KEY_FLAG, |
363 | NULL, &pubkey)) |
364 | goto error; |
365 | |
366 | #endif |
367 | if (!pubkey) |
368 | return CR_ERROR; |
369 | |
370 | pwlen= (unsigned int)strlen(mysql->passwd) + 1; /* include terminating zero */ |
371 | if (pwlen > MAX_PW_LEN) |
372 | goto error; |
373 | memcpy(passwd, mysql->passwd, pwlen); |
374 | |
375 | /* xor password with scramble */ |
376 | for (i=0; i < pwlen; i++) |
377 | passwd[i]^= *(mysql->scramble_buff + i % SCRAMBLE_LENGTH); |
378 | |
379 | /* encrypt scrambled password */ |
380 | #if defined(HAVE_OPENSSL) |
381 | if (RSA_public_encrypt(pwlen, (unsigned char *)passwd, rsa_enc_pw, pubkey, RSA_PKCS1_OAEP_PADDING) < 0) |
382 | goto error; |
383 | #elif defined(HAVE_WINCRYPT) |
384 | ZeroMemory(&paddingInfo, sizeof(paddingInfo)); |
385 | paddingInfo.pszAlgId = BCRYPT_SHA1_ALGORITHM; |
386 | if ((rc= BCryptEncrypt(pubkey, (PUCHAR)passwd, pwlen, &paddingInfo, NULL, 0, rsa_enc_pw, |
387 | MAX_PW_LEN, &rsa_size, BCRYPT_PAD_OAEP))) |
388 | goto error; |
389 | |
390 | #endif |
391 | if (vio->write_packet(vio, rsa_enc_pw, rsa_size)) |
392 | goto error; |
393 | |
394 | rc= CR_OK; |
395 | #endif |
396 | } |
397 | else |
398 | { |
399 | if (vio->write_packet(vio, (unsigned char *)mysql->passwd, (int)strlen(mysql->passwd) + 1)) |
400 | return CR_ERROR; |
401 | return CR_OK; |
402 | } |
403 | #if !defined(HAVE_GNUTLS) |
404 | error: |
405 | #if defined(HAVE_OPENSSL) |
406 | if (pubkey) |
407 | RSA_free(pubkey); |
408 | #elif defined(HAVE_WINCRYPT) |
409 | if (pubkey) |
410 | BCryptDestroyKey(pubkey); |
411 | if (publicKeyInfo) |
412 | LocalFree(publicKeyInfo); |
413 | #endif |
414 | free(filebuffer); |
415 | #endif |
416 | return rc; |
417 | } |
418 | /* }}} */ |
419 | |
420 | /* {{{ static int auth_caching_sha2_init */ |
421 | /* |
422 | Initialization routine |
423 | |
424 | SYNOPSIS |
425 | auth_sha256_init |
426 | unused1 |
427 | unused2 |
428 | unused3 |
429 | unused4 |
430 | |
431 | DESCRIPTION |
432 | Init function checks if the caller provides own dialog function. |
433 | The function name must be mariadb_auth_dialog or |
434 | mysql_authentication_dialog_ask. If the function cannot be found, |
435 | we will use owr own simple command line input. |
436 | |
437 | RETURN |
438 | 0 success |
439 | */ |
440 | static int auth_caching_sha2_init(char *unused1 __attribute__((unused)), |
441 | size_t unused2 __attribute__((unused)), |
442 | int unused3 __attribute__((unused)), |
443 | va_list unused4 __attribute__((unused))) |
444 | { |
445 | #if defined(HAVE_WINCRYPT) |
446 | BCryptOpenAlgorithmProvider(&Sha256Prov, BCRYPT_SHA256_ALGORITHM, NULL, 0); |
447 | BCryptOpenAlgorithmProvider(&RsaProv, BCRYPT_RSA_ALGORITHM, NULL, 0); |
448 | #endif |
449 | return 0; |
450 | } |
451 | /* }}} */ |
452 | |
453 | /* {{{ auth_caching_sha2_deinit */ |
454 | static int auth_caching_sha2_deinit() |
455 | { |
456 | #if defined(HAVE_WINCRYPT) |
457 | BCryptCloseAlgorithmProvider(Sha256Prov, 0); |
458 | BCryptCloseAlgorithmProvider(RsaProv, 0); |
459 | #endif |
460 | return 0; |
461 | } |
462 | /* }}} */ |
463 | |
464 | #endif /* defined(HAVE_OPENSSL) || defined(HAVE_WINCRYPT) || defined(HAVE_GNUTLS)*/ |
465 | |
466 | |