"Fossies" - the Fresh Open Source Software Archive

Member "mariadb-connector-c-3.0.8-src/plugins/auth/caching_sha2_pw.c" (18 Dec 2018, 12883 Bytes) of package /linux/misc/mariadb-connector-c-3.0.8-src.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    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_SCHANNEL) || defined(HAVE_GNUTLS)
   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_GNUTLS)
   50 #include <gnutls/gnutls.h>
   51 #elif defined(HAVE_SCHANNEL)
   52 #include <windows.h>
   53 #include <wincrypt.h>
   54 #include <bcrypt.h>
   55 #pragma comment(lib, "bcrypt.lib")
   56 #pragma comment(lib, "crypt32.lib")
   57 extern BCRYPT_ALG_HANDLE RsaProv;
   58 extern BCRYPT_ALG_HANDLE Sha256Prov;
   59 #endif
   60 
   61 #include <ma_crypt.h>
   62 
   63 #define MAX_PW_LEN 1024
   64 
   65 #define REQUEST_PUBLIC_KEY     2
   66 #define CACHED_LOGIN_SUCCEEDED 3
   67 #define RSA_LOGIN_REQUIRED 4
   68 
   69 /* MySQL server allows requesting public key only for non secure connections.
   70    secure connections are:
   71      - TLS/SSL connections
   72      - unix_socket connections
   73 */
   74 static unsigned char is_connection_secure(MYSQL *mysql)
   75 {
   76   if (mysql->options.use_ssl ||
   77       mysql->net.pvio->type != PVIO_TYPE_SOCKET)
   78     return 1;
   79   return 0;
   80 }
   81 
   82 static int ma_sha256_scramble(unsigned char *scramble, size_t scramble_len,
   83                               unsigned char *source, size_t source_len,
   84                               unsigned char *salt, size_t salt_len)
   85 {
   86   unsigned char digest1[MA_SHA256_HASH_SIZE],
   87                 digest2[MA_SHA256_HASH_SIZE],
   88                 new_scramble[MA_SHA256_HASH_SIZE];
   89 #ifdef HAVE_SCHANNEL
   90   MA_HASH_CTX myctx;
   91   MA_HASH_CTX *ctx= &myctx;
   92 #else
   93   MA_HASH_CTX *ctx = NULL;
   94 #endif
   95   size_t i;
   96 
   97   /* check if all specified lenghts are valid */
   98   if (!scramble_len || !source_len || !salt_len)
   99     return 1;
  100 
  101 
  102   /* Step1: create sha256 from source */
  103   if (!(ctx= ma_hash_new(MA_HASH_SHA256, ctx)))
  104     return 1;
  105   ma_hash_input(ctx, source, source_len);
  106   ma_hash_result(ctx, digest1);
  107   ma_hash_free(ctx);
  108 #ifndef HAVE_SCHANNEL
  109   ctx = NULL;
  110 #endif
  111 
  112   /* Step2: create sha256 digest from digest1 */
  113   if (!(ctx= ma_hash_new(MA_HASH_SHA256, ctx)))
  114     return 1;
  115   ma_hash_input(ctx, digest1, MA_SHA256_HASH_SIZE);
  116   ma_hash_result(ctx, digest2);
  117   ma_hash_free(ctx);
  118 #ifndef HAVE_SCHANNEL
  119   ctx = NULL;
  120 #endif
  121 
  122   /* Step3: create sha256 digest from digest2 + salt */
  123   if (!(ctx= ma_hash_new(MA_HASH_SHA256, ctx)))
  124     return 1;
  125   ma_hash_input(ctx, digest2, MA_SHA256_HASH_SIZE);
  126   ma_hash_input(ctx, salt, salt_len);
  127   ma_hash_result(ctx, new_scramble);
  128   ma_hash_free(ctx);
  129 
  130   /* Step4: xor(digest1, scramble1) */
  131   for (i= 0; i < scramble_len; i++)
  132     scramble[i]= digest1[i] ^ new_scramble[i];
  133   return 0;
  134 }
  135 
  136 /* function prototypes */
  137 static int auth_caching_sha2_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql);
  138 static int auth_caching_sha2_deinit();
  139 static int auth_caching_sha2_init(char *unused1,
  140     size_t unused2,
  141     int unused3,
  142     va_list);
  143 
  144 
  145 #ifndef PLUGIN_DYNAMIC
  146 struct st_mysql_client_plugin_AUTHENTICATION caching_sha2_password_client_plugin=
  147 #else
  148 struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ =
  149 #endif
  150 {
  151   MYSQL_CLIENT_AUTHENTICATION_PLUGIN,
  152   MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION,
  153   "caching_sha2_password",
  154   "Georg Richter",
  155   "Caching SHA2 Authentication Plugin",
  156   {0,1,0},
  157   "LGPL",
  158   NULL,
  159   auth_caching_sha2_init,
  160   auth_caching_sha2_deinit,
  161   NULL,
  162   auth_caching_sha2_client
  163 };
  164 
  165 #ifdef HAVE_SCHANNEL
  166 static LPBYTE ma_load_pem(const char *buffer, DWORD *buffer_len)
  167 {
  168   LPBYTE der_buffer= NULL;
  169   DWORD der_buffer_length;
  170 
  171   if (buffer_len == NULL || *buffer_len == 0)
  172     return NULL;
  173   /* calculate the length of DER binary */
  174   if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER,
  175         NULL, &der_buffer_length, NULL, NULL))
  176     goto end;
  177   /* allocate DER binary buffer */
  178   if (!(der_buffer= (LPBYTE)malloc(der_buffer_length)))
  179     goto end;
  180   /* convert to DER binary */
  181   if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER,
  182         der_buffer, &der_buffer_length, NULL, NULL))
  183     goto end;
  184 
  185   *buffer_len= der_buffer_length;
  186 
  187   return der_buffer;
  188 
  189 end:
  190   if (der_buffer)
  191     free(der_buffer);
  192   *buffer_len= 0;
  193   return NULL;
  194 }
  195 #endif
  196 
  197 char *load_pub_key_file(const char *filename, int *pub_key_size)
  198 {
  199   FILE *fp= NULL;
  200   char *buffer= NULL;
  201   unsigned char error= 1;
  202 
  203   if (!pub_key_size)
  204     return NULL;
  205 
  206   if (!(fp= fopen(filename, "r")))
  207     goto end;
  208 
  209   if (fseek(fp, 0, SEEK_END))
  210     goto end;
  211 
  212   *pub_key_size= ftell(fp);
  213   rewind(fp);
  214 
  215   if (!(buffer= malloc(*pub_key_size + 1)))
  216     goto end;
  217 
  218   if (!fread(buffer, *pub_key_size, 1, fp))
  219     goto end;
  220 
  221   error= 0;
  222 
  223 end:
  224   if (fp)
  225     fclose(fp);
  226   if (error && buffer)
  227   {
  228     free(buffer);
  229     buffer= NULL;
  230   }
  231   return buffer;
  232 }
  233 
  234 
  235 static int auth_caching_sha2_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
  236 {
  237   unsigned char *packet;
  238   int packet_length;
  239   int rc= CR_ERROR;
  240 #if !defined(HAVE_GNUTLS)
  241   char passwd[MAX_PW_LEN];
  242   unsigned char rsa_enc_pw[MAX_PW_LEN];
  243 #ifdef HAVE_OPENSSL
  244   int rsa_size;
  245 #else
  246   ULONG rsa_size;
  247 #endif
  248   unsigned int pwlen, i;
  249   char *filebuffer= NULL;
  250 #endif
  251   unsigned char buf[MA_SHA256_HASH_SIZE];
  252 
  253 #if defined(HAVE_OPENSSL)
  254   RSA *pubkey= NULL;
  255   BIO *bio;
  256 #elif defined(HAVE_SCHANNEL)
  257   BCRYPT_KEY_HANDLE pubkey= 0;
  258   BCRYPT_OAEP_PADDING_INFO paddingInfo;
  259   LPBYTE der_buffer= NULL;
  260   DWORD der_buffer_len= 0;
  261   CERT_PUBLIC_KEY_INFO *publicKeyInfo= NULL;
  262   DWORD publicKeyInfoLen;
  263 #endif
  264 
  265   /* read error */
  266   if ((packet_length= vio->read_packet(vio, &packet)) < 0)
  267     return CR_ERROR;
  268 
  269   if (packet_length != SCRAMBLE_LENGTH + 1)
  270     return CR_SERVER_HANDSHAKE_ERR;
  271 
  272   memmove(mysql->scramble_buff, packet, SCRAMBLE_LENGTH);
  273   mysql->scramble_buff[SCRAMBLE_LENGTH]= 0;
  274 
  275   /* if a tls session is active we need to send plain password */
  276   if (mysql->client_flag & CLIENT_SSL)
  277   {
  278     if (vio->write_packet(vio, (unsigned char *)mysql->passwd, (int)strlen(mysql->passwd) + 1))
  279       return CR_ERROR;
  280     return CR_OK;
  281   }
  282 
  283   /* send empty packet if no password was provided */
  284   if (!mysql->passwd || !mysql->passwd[0])
  285   {
  286     if (vio->write_packet(vio, 0, 0))
  287       return CR_ERROR;
  288     return CR_OK;
  289   }
  290 
  291   /* This is the normal authentication, if the host/user key is already in server
  292      cache. In case authentication will fail, we will not return an error but will
  293      try to connect via RSA encryption.
  294   */
  295   if (ma_sha256_scramble(buf, MA_SHA256_HASH_SIZE,
  296                          (unsigned char *)mysql->passwd, strlen(mysql->passwd),
  297                          (unsigned char *)mysql->scramble_buff, SCRAMBLE_LENGTH))
  298     return CR_ERROR;
  299 
  300   if (vio->write_packet(vio, buf, MA_SHA256_HASH_SIZE))
  301     return CR_ERROR;
  302   if ((packet_length=vio->read_packet(vio, &packet)) == -1)
  303     return CR_ERROR;
  304   if (packet_length == 1)
  305   {
  306     switch (*packet) {
  307     case CACHED_LOGIN_SUCCEEDED:
  308       return CR_OK;
  309     case RSA_LOGIN_REQUIRED:
  310       break;
  311     default:
  312       return CR_ERROR;
  313     }
  314   }
  315 
  316   if (!is_connection_secure(mysql))
  317   {
  318 #if defined(HAVE_GNUTLS)
  319      mysql->methods->set_error(mysql, CR_AUTH_PLUGIN_ERR, "HY000", 
  320                                "RSA Encrytion not supported - caching_sha2_password plugin was built with GnuTLS support");
  321      return CR_ERROR;
  322 #else
  323     /* read public key file (if specified) */
  324     if (mysql->options.extension &&
  325         mysql->options.extension->server_public_key)
  326     {
  327       filebuffer= load_pub_key_file(mysql->options.extension->server_public_key,
  328                                     &packet_length);
  329     }
  330 
  331     /* if no public key file was specified or if we couldn't read the file,
  332        we ask server to send public key */
  333     if (!filebuffer)
  334     {
  335       unsigned char request= REQUEST_PUBLIC_KEY;
  336       if (vio->write_packet(vio, &request, 1) ||
  337          (packet_length=vio->read_packet(vio, &packet)) == -1)
  338       {
  339         mysql->methods->set_error(mysql, CR_AUTH_PLUGIN_ERR, "HY000", "Couldn't read RSA public key from server");
  340         return CR_ERROR;
  341       }
  342     }
  343 #if defined(HAVE_OPENSSL)
  344     bio= BIO_new_mem_buf(filebuffer ? (unsigned char *)filebuffer : packet,
  345                          packet_length);
  346     if ((pubkey= PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL)))
  347       rsa_size= RSA_size(pubkey);
  348     BIO_free(bio);
  349     ERR_clear_error();
  350 #elif defined(HAVE_SCHANNEL)
  351     der_buffer_len= packet_length;
  352     /* Load pem and convert it to binary object. New length will be returned
  353        in der_buffer_len */
  354     if (!(der_buffer= ma_load_pem(filebuffer ? filebuffer : (char *)packet, &der_buffer_len)))
  355       goto error;
  356 
  357     /* Create context and load public key */
  358     if (!CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
  359                              der_buffer, der_buffer_len,
  360                              CRYPT_DECODE_ALLOC_FLAG, NULL,
  361                              &publicKeyInfo, &publicKeyInfoLen))
  362       goto error;
  363     free(der_buffer);
  364 
  365     /* Import public key as cng key */
  366     if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, publicKeyInfo,
  367                                      CRYPT_OID_INFO_PUBKEY_ENCRYPT_KEY_FLAG,
  368                                      NULL, &pubkey))
  369       goto error;
  370 
  371 #endif
  372     if (!pubkey)
  373       return CR_ERROR;
  374 
  375     pwlen= (unsigned int)strlen(mysql->passwd) + 1;  /* include terminating zero */
  376     if (pwlen > MAX_PW_LEN)
  377       goto error;
  378     memcpy(passwd, mysql->passwd, pwlen);
  379 
  380     /* xor password with scramble */
  381     for (i=0; i < pwlen; i++)
  382       passwd[i]^= *(mysql->scramble_buff + i % SCRAMBLE_LENGTH);
  383 
  384     /* encrypt scrambled password */
  385 #if defined(HAVE_OPENSSL)
  386     if (RSA_public_encrypt(pwlen, (unsigned char *)passwd, rsa_enc_pw, pubkey, RSA_PKCS1_OAEP_PADDING) < 0)
  387       goto error;
  388 #elif defined(HAVE_SCHANNEL)
  389     ZeroMemory(&paddingInfo, sizeof(paddingInfo));
  390     paddingInfo.pszAlgId = BCRYPT_SHA1_ALGORITHM;
  391     if ((rc= BCryptEncrypt(pubkey, (PUCHAR)passwd, pwlen, &paddingInfo, NULL, 0, rsa_enc_pw,
  392                      MAX_PW_LEN, &rsa_size, BCRYPT_PAD_OAEP)))
  393       goto error;
  394 
  395 #endif
  396     if (vio->write_packet(vio, rsa_enc_pw, rsa_size))
  397       goto error;
  398 
  399     rc= CR_OK;
  400 #endif
  401   }
  402   else
  403   {
  404     if (vio->write_packet(vio, (unsigned char *)mysql->passwd, (int)strlen(mysql->passwd) + 1))
  405       return CR_ERROR;
  406     return CR_OK;
  407   }
  408 #if !defined(HAVE_GNUTLS)
  409 error:
  410 #if defined(HAVE_OPENSSL)
  411   if (pubkey)
  412     RSA_free(pubkey);
  413 #elif defined(HAVE_SCHANNEL)
  414   if (pubkey)
  415     BCryptDestroyKey(pubkey);
  416   if (publicKeyInfo)
  417     LocalFree(publicKeyInfo);
  418 #endif
  419   free(filebuffer);
  420 #endif
  421   return rc;
  422 }
  423 /* }}} */
  424 
  425 /* {{{ static int auth_caching_sha2_init */
  426 /*
  427    Initialization routine
  428 
  429    SYNOPSIS
  430    auth_sha256_init
  431    unused1
  432    unused2
  433    unused3
  434    unused4
  435 
  436    DESCRIPTION
  437    Init function checks if the caller provides own dialog function.
  438    The function name must be mariadb_auth_dialog or
  439    mysql_authentication_dialog_ask. If the function cannot be found,
  440    we will use owr own simple command line input.
  441 
  442    RETURN
  443    0           success
  444  */
  445 static int auth_caching_sha2_init(char *unused1 __attribute__((unused)),
  446     size_t unused2  __attribute__((unused)),
  447     int unused3     __attribute__((unused)),
  448     va_list unused4 __attribute__((unused)))
  449 {
  450 #if defined(HAVE_SCHANNEL)
  451   BCryptOpenAlgorithmProvider(&Sha256Prov, BCRYPT_SHA256_ALGORITHM, NULL, 0);
  452   BCryptOpenAlgorithmProvider(&RsaProv, BCRYPT_RSA_ALGORITHM, NULL, 0);
  453 #endif
  454   return 0;
  455 }
  456 /* }}} */
  457 
  458 /* {{{ auth_caching_sha2_deinit */
  459 static int auth_caching_sha2_deinit()
  460 {
  461 #if defined(HAVE_SCHANNEL)
  462   BCryptCloseAlgorithmProvider(Sha256Prov, 0);
  463   BCryptCloseAlgorithmProvider(RsaProv, 0);
  464 #endif
  465   return 0;
  466 }
  467 /* }}} */
  468 
  469 #endif  /* defined(HAVE_OPENSSL) || defined(HAVE_WINCRYPT) || defined(HAVE_GNUTLS)*/
  470