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 */ |
56 | static int auth_sha256_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); |
57 | static int auth_sha256_init(char *unused1, |
58 | size_t unused2, |
59 | int unused3, |
60 | va_list); |
61 | |
62 | |
63 | #ifndef PLUGIN_DYNAMIC |
64 | struct st_mysql_client_plugin_AUTHENTICATION sha256_password_client_plugin= |
65 | #else |
66 | struct 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 |
84 | static 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 | |
107 | end: |
108 | if (der_buffer) |
109 | LocalFree(der_buffer); |
110 | *buffer_len= 0; |
111 | return NULL; |
112 | } |
113 | #endif |
114 | |
115 | char *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 | |
141 | end: |
142 | if (fp) |
143 | fclose(fp); |
144 | if (error && buffer) |
145 | { |
146 | free(buffer); |
147 | buffer= NULL; |
148 | } |
149 | return buffer; |
150 | } |
151 | |
152 | |
153 | static 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; |
285 | error: |
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 | */ |
319 | static 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 | |