1 /*
2    Copyright (c) 2017, 2021, MariaDB
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; version 2 of the License.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software
15    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
16 
17 #include <mysql/plugin_auth.h>
18 #include <mysqld_error.h>
19 #include "common.h"
20 
21 #if !defined(__attribute__) && !defined(__GNUC__)
22 #define __attribute__(A)
23 #endif
24 
25 #define PASSWORD_LEN_BUF 44 /* base64 of 32 bytes */
26 #define PASSWORD_LEN 43     /* we won't store the last byte, padding '=' */
27 
28 #define CRYPTO_LONGS (CRYPTO_BYTES/sizeof(long))
29 #define NONCE_LONGS  (NONCE_BYTES/sizeof(long))
30 
31 /************************** SERVER *************************************/
32 
33 static int loaded= 0;
34 
auth(MYSQL_PLUGIN_VIO * vio,MYSQL_SERVER_AUTH_INFO * info)35 static int auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
36 {
37   int pkt_len;
38   unsigned long nonce[CRYPTO_LONGS + NONCE_LONGS];
39   unsigned char *pkt, *reply= (unsigned char*)nonce;
40 
41   info->password_used= PASSWORD_USED_YES;
42 
43   /* prepare random nonce */
44   if (my_random_bytes((unsigned char *)nonce, (int)sizeof(nonce)))
45     return CR_ERROR; // eh? OpenSSL error
46 
47   /* send it */
48   if (vio->write_packet(vio, reply + CRYPTO_BYTES, NONCE_BYTES))
49     return CR_AUTH_HANDSHAKE;
50 
51   /* read the signature */
52   if ((pkt_len= vio->read_packet(vio, &pkt)) != CRYPTO_BYTES)
53     return CR_AUTH_HANDSHAKE;
54   memcpy(reply, pkt, CRYPTO_BYTES);
55 
56   if (crypto_sign_open(reply, CRYPTO_BYTES + NONCE_BYTES,
57                        (unsigned char*)info->auth_string))
58     return CR_AUTH_USER_CREDENTIALS; // wrong password provided by the user
59 
60   return CR_OK;
61 }
62 
compute_password_digest(const char * pw,size_t pwlen,char * d,size_t * dlen)63 static int compute_password_digest(const char *pw, size_t pwlen,
64                                    char *d, size_t *dlen)
65 {
66   unsigned char pk[CRYPTO_PUBLICKEYBYTES];
67   if (*dlen < PASSWORD_LEN || pwlen == 0)
68     return 1;
69   *dlen= PASSWORD_LEN;
70   crypto_sign_keypair(pk, (unsigned char*)pw, pwlen);
71   my_base64_encode(pk, CRYPTO_PUBLICKEYBYTES, d);
72   return 0;
73 }
74 
digest_to_binary(const char * d,size_t dlen,unsigned char * b,size_t * blen)75 static int digest_to_binary(const char *d, size_t dlen,
76                             unsigned char *b, size_t *blen)
77 {
78   char pw[PASSWORD_LEN_BUF];
79 
80   if (*blen < CRYPTO_PUBLICKEYBYTES || dlen != PASSWORD_LEN)
81   {
82     my_printf_error(ER_PASSWD_LENGTH, "Password hash should be %d characters long", 0, PASSWORD_LEN);
83     return 1;
84   }
85 
86   *blen= CRYPTO_PUBLICKEYBYTES;
87   memcpy(pw, d, PASSWORD_LEN);
88   pw[PASSWORD_LEN]= '=';
89   if (my_base64_decode(pw, PASSWORD_LEN_BUF, b, 0, 0) == CRYPTO_PUBLICKEYBYTES)
90     return 0;
91   my_printf_error(ER_PASSWD_LENGTH, "Password hash should be base64 encoded", 0);
92   return 1;
93 }
94 
95 static struct st_mysql_auth info =
96 {
97   MYSQL_AUTHENTICATION_INTERFACE_VERSION,
98   "client_ed25519",
99   auth,
100   compute_password_digest,
101   digest_to_binary
102 };
103 
init(void * p)104 static int init(void *p __attribute__((unused)))
105 {
106   loaded= 1;
107   return 0;
108 }
109 
deinit(void * p)110 static int deinit(void *p __attribute__((unused)))
111 {
112   loaded= 0;
113   return 0;
114 }
115 
maria_declare_plugin(ed25519)116 maria_declare_plugin(ed25519)
117 {
118   MYSQL_AUTHENTICATION_PLUGIN,
119   &info,
120   "ed25519",
121   "Sergei Golubchik",
122   "Elliptic curve ED25519 based authentication",
123   PLUGIN_LICENSE_GPL,
124   init,
125   deinit,
126   0x0101,
127   NULL,
128   NULL,
129   "1.1",
130   MariaDB_PLUGIN_MATURITY_STABLE
131 }
132 maria_declare_plugin_end;
133 
134 /************************** UDF ****************************************/
135 MYSQL_PLUGIN_EXPORT
ed25519_password(UDF_INIT * initid,UDF_ARGS * args,char * result,unsigned long * length,char * is_null,char * error)136 char *ed25519_password(UDF_INIT *initid __attribute__((unused)),
137                        UDF_ARGS *args, char *result, unsigned long *length,
138                        char *is_null, char *error __attribute__((unused)))
139 {
140   unsigned char pk[CRYPTO_PUBLICKEYBYTES];
141 
142   if ((*is_null= !args->args[0]))
143     return NULL;
144 
145   *length= PASSWORD_LEN;
146   crypto_sign_keypair(pk, (unsigned char*)args->args[0], args->lengths[0]);
147   my_base64_encode(pk, CRYPTO_PUBLICKEYBYTES, result);
148   return result;
149 }
150 
151 /*
152   At least one of _init/_deinit is needed unless the server is started
153   with --allow_suspicious_udfs.
154 */
155 MYSQL_PLUGIN_EXPORT
ed25519_password_init(UDF_INIT * initid,UDF_ARGS * args,char * message)156 my_bool ed25519_password_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
157 {
158   if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
159   {
160     strcpy(message,"Wrong arguments to ed25519_password()");
161     return 1;
162   }
163   if (!loaded)
164   {
165     /* cannot work unless the plugin is loaded, we need services. */
166     strcpy(message,"Authentication plugin ed25519 is not loaded");
167     return 1;
168   }
169   initid->max_length= PASSWORD_LEN_BUF;
170   return 0;
171 }
172