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 */ |
58 | static int auth_sha256_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); |
59 | static int auth_sha256_init(char *unused1, |
60 | size_t unused2, |
61 | int unused3, |
62 | va_list); |
63 | |
64 | |
65 | #ifndef PLUGIN_DYNAMIC |
66 | struct st_mysql_client_plugin_AUTHENTICATION sha256_password_client_plugin= |
67 | #else |
68 | struct 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 |
86 | static 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 | |
109 | end: |
110 | if (der_buffer) |
111 | LocalFree(der_buffer); |
112 | *buffer_len= 0; |
113 | return NULL; |
114 | } |
115 | #endif |
116 | |
117 | static 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 | |
151 | end: |
152 | if (fp) |
153 | fclose(fp); |
154 | if (error && buffer) |
155 | { |
156 | free(buffer); |
157 | buffer= NULL; |
158 | } |
159 | return buffer; |
160 | } |
161 | |
162 | |
163 | static 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; |
295 | error: |
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 | */ |
329 | static 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 | |