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