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#undef HAVE_GNUTLS
25#undef HAVE_OPENSSL
26#define HAVE_WINCRYPT
27#pragma comment(lib, "crypt32.lib")
28#pragma comment(lib, "ws2_32.lib")
29#endif
30
31#if defined(HAVE_OPENSSL) || defined(HAVE_WINCRYPT)
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(WIN32)
48#include <wincrypt.h>
49#elif defined(HAVE_OPENSSL)
50#include <openssl/rsa.h>
51#include <openssl/pem.h>
52#include <openssl/err.h>
53#endif
54
55#define MAX_PW_LEN 1024
56
57/* function prototypes */
58static int auth_sha256_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql);
59static int auth_sha256_init(char *unused1,
60 size_t unused2,
61 int unused3,
62 va_list);
63
64
65#ifndef PLUGIN_DYNAMIC
66struct st_mysql_client_plugin_AUTHENTICATION sha256_password_client_plugin=
67#else
68struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ =
69#endif
70{
71 MYSQL_CLIENT_AUTHENTICATION_PLUGIN,
72 MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION,
73 "sha256_password",
74 "Georg Richter",
75 "SHA256 Authentication Plugin",
76 {0,1,0},
77 "LGPL",
78 NULL,
79 auth_sha256_init,
80 NULL,
81 NULL,
82 auth_sha256_client
83};
84
85#ifdef HAVE_WINCRYPT
86static LPBYTE ma_load_pem(const char *buffer, DWORD *buffer_len)
87{
88 LPBYTE der_buffer= NULL;
89 DWORD der_buffer_length= 0;
90
91 if (buffer_len == NULL || *buffer_len == 0)
92 return NULL;
93 /* calculate the length of DER binary */
94 if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER,
95 NULL, &der_buffer_length, NULL, NULL))
96 goto end;
97 /* allocate DER binary buffer */
98 if (!(der_buffer= (LPBYTE)LocalAlloc(0, der_buffer_length)))
99 goto end;
100 /* convert to DER binary */
101 if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER,
102 der_buffer, &der_buffer_length, NULL, NULL))
103 goto end;
104
105 *buffer_len= der_buffer_length;
106
107 return der_buffer;
108
109end:
110 if (der_buffer)
111 LocalFree(der_buffer);
112 *buffer_len= 0;
113 return NULL;
114}
115#endif
116
117static char *load_pub_key_file(const char *filename, int *pub_key_size)
118{
119 FILE *fp= NULL;
120 char *buffer= NULL;
121 unsigned char error= 1;
122 size_t bytes_read= 0;
123 long fsize= 0;
124
125 if (!pub_key_size)
126 return NULL;
127
128 if (!(fp= fopen(filename, "r")))
129 goto end;
130
131 if (fseek(fp, 0, SEEK_END))
132 goto end;
133
134 fsize= ftell(fp);
135 if (fsize < 0)
136 goto end;
137
138 rewind(fp);
139
140 if (!(buffer= malloc(fsize + 1)))
141 goto end;
142
143 bytes_read= fread(buffer, 1, (size_t)fsize, fp);
144 if (bytes_read < (size_t)fsize)
145 goto end;
146
147 *pub_key_size= (int)bytes_read;
148
149 error= 0;
150
151end:
152 if (fp)
153 fclose(fp);
154 if (error && buffer)
155 {
156 free(buffer);
157 buffer= NULL;
158 }
159 return buffer;
160}
161
162
163static int auth_sha256_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
164{
165 unsigned char *packet;
166 int packet_length;
167 int rc= CR_ERROR;
168 char passwd[MAX_PW_LEN];
169 unsigned char rsa_enc_pw[MAX_PW_LEN];
170 unsigned int rsa_size;
171 unsigned int pwlen, i;
172
173#if defined(HAVE_OPENSSL)
174 RSA *pubkey= NULL;
175 BIO *bio;
176#elif defined(HAVE_WINCRYPT)
177 HCRYPTKEY pubkey= 0;
178 HCRYPTPROV hProv= 0;
179 LPBYTE der_buffer= NULL;
180 DWORD der_buffer_len= 0;
181 CERT_PUBLIC_KEY_INFO *publicKeyInfo= NULL;
182 DWORD ParamSize= sizeof(DWORD);
183 int publicKeyInfoLen= 0;
184#endif
185 char *filebuffer= NULL;
186
187 /* read error */
188 if ((packet_length= vio->read_packet(vio, &packet)) < 0)
189 return CR_ERROR;
190
191 if (packet_length != SCRAMBLE_LENGTH + 1)
192 return CR_SERVER_HANDSHAKE_ERR;
193
194 memmove(mysql->scramble_buff, packet, SCRAMBLE_LENGTH);
195 mysql->scramble_buff[SCRAMBLE_LENGTH]= 0;
196
197 /* if a tls session is active we need to send plain password */
198 if (mysql->client_flag & CLIENT_SSL)
199 {
200 if (vio->write_packet(vio, (unsigned char *)mysql->passwd, (int)strlen(mysql->passwd) + 1))
201 return CR_ERROR;
202 return CR_OK;
203 }
204
205 /* send empty packet if no password was provided */
206 if (!mysql->passwd || !mysql->passwd[0])
207 {
208 if (vio->write_packet(vio, 0, 0))
209 return CR_ERROR;
210 return CR_OK;
211 }
212
213 /* read public key file (if specified) */
214 if (mysql->options.extension &&
215 mysql->options.extension->server_public_key)
216 {
217 filebuffer= load_pub_key_file(mysql->options.extension->server_public_key,
218 &packet_length);
219 }
220
221 /* if no public key file was specified or if we couldn't read the file,
222 we ask server to send public key */
223 if (!filebuffer)
224 {
225 unsigned char buf= 1;
226 if (vio->write_packet(vio, &buf, 1))
227 return CR_ERROR;
228 if ((packet_length=vio->read_packet(vio, &packet)) == -1)
229 return CR_ERROR;
230 }
231#if defined(HAVE_OPENSSL)
232 bio= BIO_new_mem_buf(filebuffer ? (unsigned char *)filebuffer : packet,
233 packet_length);
234 if ((pubkey= PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL)))
235 rsa_size= RSA_size(pubkey);
236 BIO_free(bio);
237 ERR_clear_error();
238#elif defined(HAVE_WINCRYPT)
239 der_buffer_len= packet_length;
240 /* Load pem and convert it to binary object. New length will be returned
241 in der_buffer_len */
242 if (!(der_buffer= ma_load_pem(filebuffer ? filebuffer : (char *)packet, &der_buffer_len)))
243 goto error;
244
245 /* Create context and load public key */
246 if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
247 der_buffer, der_buffer_len,
248 CRYPT_DECODE_ALLOC_FLAG, NULL,
249 &publicKeyInfo, (DWORD *)&publicKeyInfoLen))
250 goto error;
251 LocalFree(der_buffer);
252
253 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL,
254 CRYPT_VERIFYCONTEXT))
255 goto error;
256 if (!CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING,
257 publicKeyInfo, &pubkey))
258 goto error;
259
260 /* Get rsa_size */
261 CryptGetKeyParam(pubkey, KP_KEYLEN, (BYTE *)&rsa_size, &ParamSize, 0);
262 rsa_size /= 8;
263#endif
264 if (!pubkey)
265 return CR_ERROR;
266
267 pwlen= (unsigned int)strlen(mysql->passwd) + 1; /* include terminating zero */
268 if (pwlen > MAX_PW_LEN)
269 goto error;
270 memcpy(passwd, mysql->passwd, pwlen);
271
272 /* xor password with scramble */
273 for (i=0; i < pwlen; i++)
274 passwd[i]^= *(mysql->scramble_buff + i % SCRAMBLE_LENGTH);
275
276 /* encrypt scrambled password */
277#if defined(HAVE_OPENSSL)
278 if (RSA_public_encrypt(pwlen, (unsigned char *)passwd, rsa_enc_pw, pubkey, RSA_PKCS1_OAEP_PADDING) < 0)
279 goto error;
280#elif defined(HAVE_WINCRYPT)
281 if (!CryptEncrypt(pubkey, 0, TRUE, CRYPT_OAEP, (BYTE *)passwd, (DWORD *)&pwlen, MAX_PW_LEN))
282 goto error;
283 /* Windows encrypts as little-endian, while server (openssl) expects
284 big-endian, so we have to revert the string */
285 for (i= 0; i < rsa_size / 2; i++)
286 {
287 rsa_enc_pw[i]= passwd[rsa_size - 1 - i];
288 rsa_enc_pw[rsa_size - 1 - i]= passwd[i];
289 }
290#endif
291 if (vio->write_packet(vio, rsa_enc_pw, rsa_size))
292 goto error;
293
294 rc= CR_OK;
295error:
296#if defined(HAVE_OPENSSL)
297 if (pubkey)
298 RSA_free(pubkey);
299#elif defined(HAVE_WINCRYPT)
300 CryptReleaseContext(hProv, 0);
301 if (publicKeyInfo)
302 LocalFree(publicKeyInfo);
303#endif
304 free(filebuffer);
305 return rc;
306}
307/* }}} */
308
309/* {{{ static int auth_sha256_init */
310/*
311 Initialization routine
312
313 SYNOPSIS
314 auth_sha256_init
315 unused1
316 unused2
317 unused3
318 unused4
319
320 DESCRIPTION
321 Init function checks if the caller provides own dialog function.
322 The function name must be mariadb_auth_dialog or
323 mysql_authentication_dialog_ask. If the function cannot be found,
324 we will use owr own simple command line input.
325
326 RETURN
327 0 success
328 */
329static int auth_sha256_init(char *unused1 __attribute__((unused)),
330 size_t unused2 __attribute__((unused)),
331 int unused3 __attribute__((unused)),
332 va_list unused4 __attribute__((unused)))
333{
334 return 0;
335}
336/* }}} */
337
338#endif /* defined(HAVE_OPENSSL) || defined(HAVE_WINCRYPT) */
339