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")
59extern BCRYPT_ALG_HANDLE RsaProv;
60extern 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*/
76static 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
84static 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 */
139static int auth_caching_sha2_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql);
140static int auth_caching_sha2_deinit();
141static int auth_caching_sha2_init(char *unused1,
142 size_t unused2,
143 int unused3,
144 va_list);
145
146
147#ifndef PLUGIN_DYNAMIC
148struct st_mysql_client_plugin_AUTHENTICATION caching_sha2_password_client_plugin=
149#else
150struct 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
168static 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
191end:
192 if (der_buffer)
193 free(der_buffer);
194 *buffer_len= 0;
195 return NULL;
196}
197#endif
198
199#ifndef HAVE_GNUTLS
200static 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
226end:
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
238static 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)
404error:
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 */
440static 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 */
454static 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