1 /* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    Without limiting anything contained in the foregoing, this file,
15    which is part of C Driver for MySQL (Connector/C), is also subject to the
16    Universal FOSS Exception, version 1.0, a copy of which can be found at
17    http://oss.oracle.com/licenses/universal-foss-exception.
18 
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22    GNU General Public License, version 2.0, for more details.
23 
24    You should have received a copy of the GNU General Public License
25    along with this program; if not, write to the Free Software
26    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */
27 
28 // First include (the generated) my_config.h, to get correct platform defines.
29 #include "my_config.h"
30 
31 #if defined(HAVE_OPENSSL)
32 #include "crypt_genhash_impl.h"
33 #include "mysql/client_authentication.h"
34 #include "m_ctype.h"
35 #include "sql_common.h"
36 #include "errmsg.h"
37 #include "sql_string.h"
38 
39 #include <string.h>
40 #include <stdarg.h>
41 #include <openssl/rsa.h>
42 #include <openssl/pem.h>
43 #include <openssl/err.h>
44 #if defined(_WIN32) && !defined(_OPENSSL_Applink) && defined(HAVE_OPENSSL_APPLINK_C)
45 #include <openssl/applink.c>
46 #endif
47 #include "mysql/service_my_plugin_log.h"
48 
49 #define MAX_CIPHER_LENGTH 1024
50 
51 mysql_mutex_t g_public_key_mutex;
52 
sha256_password_init(char * a,size_t b,int c,va_list d)53 int sha256_password_init(char *a, size_t b, int c, va_list d)
54 {
55   mysql_mutex_init(0,&g_public_key_mutex, MY_MUTEX_INIT_SLOW);
56   return 0;
57 }
58 
sha256_password_deinit(void)59 int sha256_password_deinit(void)
60 {
61   mysql_mutex_destroy(&g_public_key_mutex);
62   return 0;
63 }
64 
65 
66 /**
67   Reads and parse RSA public key data from a file.
68 
69   @param mysql connection handle with file path data
70 
71   @return Pointer to the RSA public key storage buffer
72 */
73 
rsa_init(MYSQL * mysql)74 RSA *rsa_init(MYSQL *mysql)
75 {
76   static RSA *g_public_key= NULL;
77   RSA *key= NULL;
78 
79   mysql_mutex_lock(&g_public_key_mutex);
80   key= g_public_key;
81   mysql_mutex_unlock(&g_public_key_mutex);
82 
83   if (key != NULL)
84     return key;
85 
86   FILE *pub_key_file= NULL;
87 
88   if (mysql->options.extension != NULL &&
89       mysql->options.extension->server_public_key_path != NULL &&
90       mysql->options.extension->server_public_key_path[0] != '\0')
91   {
92     pub_key_file= fopen(mysql->options.extension->server_public_key_path,
93                         "r");
94   }
95   /* No public key is used; return 0 without errors to indicate this. */
96   else
97     return 0;
98 
99   if (pub_key_file == NULL)
100   {
101     /*
102       If a key path was submitted but no key located then we print an error
103       message. Else we just report that there is no public key.
104     */
105     fprintf(stderr,"Can't locate server public key '%s'\n",
106               mysql->options.extension->server_public_key_path);
107 
108     return 0;
109   }
110 
111   mysql_mutex_lock(&g_public_key_mutex);
112   key= g_public_key= PEM_read_RSA_PUBKEY(pub_key_file, 0, 0, 0);
113   mysql_mutex_unlock(&g_public_key_mutex);
114   fclose(pub_key_file);
115   if (g_public_key == NULL)
116   {
117     ERR_clear_error();
118     fprintf(stderr, "Public key is not in PEM format: '%s'\n",
119             mysql->options.extension->server_public_key_path);
120     return 0;
121   }
122 
123   return key;
124 }
125 
126 /**
127   Authenticate the client using the RSA or TLS and a SHA256 salted password.
128 
129   @param vio Provides plugin access to communication channel
130   @param mysql Client connection handler
131 
132   @return Error status
133     @retval CR_ERROR An error occurred.
134     @retval CR_OK Authentication succeeded.
135 */
136 
137 extern "C"
sha256_password_auth_client(MYSQL_PLUGIN_VIO * vio,MYSQL * mysql)138 int sha256_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
139 {
140   bool uses_password= mysql->passwd[0] != 0;
141   unsigned char encrypted_password[MAX_CIPHER_LENGTH];
142   static char request_public_key= '\1';
143   RSA *public_key= NULL;
144   bool got_public_key_from_server= false;
145   bool connection_is_secure= false;
146   unsigned char scramble_pkt[20];
147   unsigned char *pkt;
148 
149 
150   DBUG_ENTER("sha256_password_auth_client");
151 
152   /*
153     Get the scramble from the server because we need it when sending encrypted
154     password.
155   */
156   if (vio->read_packet(vio, &pkt) != SCRAMBLE_LENGTH + 1)
157   {
158     DBUG_PRINT("info",("Scramble is not of correct length."));
159     DBUG_RETURN(CR_ERROR);
160   }
161   if (pkt[SCRAMBLE_LENGTH] != '\0')
162   {
163     DBUG_PRINT("info",("Missing protocol token in scramble data."));
164     DBUG_RETURN(CR_ERROR);
165   }
166   /*
167     Copy the scramble to the stack or it will be lost on the next use of the
168     net buffer.
169   */
170   memcpy(scramble_pkt, pkt, SCRAMBLE_LENGTH);
171 
172   if (mysql_get_ssl_cipher(mysql) != NULL)
173     connection_is_secure= true;
174 
175   /* If connection isn't secure attempt to get the RSA public key file */
176   if (!connection_is_secure)
177     public_key= rsa_init(mysql);
178 
179   if (!uses_password)
180   {
181     /* We're not using a password */
182     static const unsigned char zero_byte= '\0';
183     if (vio->write_packet(vio, (const unsigned char *) &zero_byte, 1))
184       DBUG_RETURN(CR_ERROR);
185   }
186   else
187   {
188     /* Password is a 0-terminated byte array ('\0' character included) */
189     unsigned int passwd_len= strlen(mysql->passwd) + 1;
190     if (!connection_is_secure)
191     {
192       /*
193         If no public key; request one from the server.
194       */
195       if (public_key == NULL)
196       {
197         if (vio->write_packet(vio, (const unsigned char *) &request_public_key,
198                               1))
199           DBUG_RETURN(CR_ERROR);
200 
201         int pkt_len= 0;
202         unsigned char *pkt;
203         if ((pkt_len= vio->read_packet(vio, &pkt)) == -1)
204           DBUG_RETURN(CR_ERROR);
205         BIO* bio= BIO_new_mem_buf(pkt, pkt_len);
206         public_key= PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
207         BIO_free(bio);
208         if (public_key == 0)
209         {
210           ERR_clear_error();
211           DBUG_RETURN(CR_ERROR);
212         }
213         got_public_key_from_server= true;
214       }
215 
216       /* Obfuscate the plain text password with the session scramble */
217       xor_string(mysql->passwd, strlen(mysql->passwd), (char *) scramble_pkt,
218                  SCRAMBLE_LENGTH);
219       /* Encrypt the password and send it to the server */
220       int cipher_length= RSA_size(public_key);
221       /*
222         When using RSA_PKCS1_OAEP_PADDING the password length must be less
223         than RSA_size(rsa) - 41.
224       */
225       if (passwd_len + 41 >= (unsigned) cipher_length)
226       {
227         /* password message is to long */
228         DBUG_RETURN(CR_ERROR);
229       }
230       RSA_public_encrypt(passwd_len, (unsigned char *) mysql->passwd,
231                          encrypted_password,
232                          public_key, RSA_PKCS1_OAEP_PADDING);
233       if (got_public_key_from_server)
234         RSA_free(public_key);
235 
236       if (vio->write_packet(vio, (uchar*) encrypted_password, cipher_length))
237         DBUG_RETURN(CR_ERROR);
238     }
239     else
240     {
241       /* The vio is encrypted already; just send the plain text passwd */
242       if (vio->write_packet(vio, (uchar*) mysql->passwd, passwd_len))
243         DBUG_RETURN(CR_ERROR);
244     }
245 
246     memset(mysql->passwd, 0, passwd_len);
247   }
248 
249   DBUG_RETURN(CR_OK);
250 }
251 
252 #endif
253