diff options
author | unknown <kostja@oak.local> | 2003-07-01 23:40:59 +0400 |
---|---|---|
committer | unknown <kostja@oak.local> | 2003-07-01 23:40:59 +0400 |
commit | dbb088b034e19e99ec209cbbc4eed3bff64172da (patch) | |
tree | cbcae0aeb3eee5a5a448084ae5f0e9b5290fac26 /sql/password.c | |
parent | b871e549eeec215bd40554431de8d21942e596d6 (diff) | |
download | mariadb-git-dbb088b034e19e99ec209cbbc4eed3bff64172da.tar.gz |
First version of new authentification procedure: now authentification is one-stage (instead of two-stage in 4.1)
For now following tasks have been done:
- PASSWORD() function was rewritten. PASSWORD() now returns SHA1
hash_stage2; for new passwords user.password contains '*'hash_stage2; sql_yacc.yy also fixed;
- password.c: new functions were implemented, old rolled back to 4.0 state
- server code was rewritten to use new authorization algorithm (check_user(), change
user, and other stuff in sql/sql_parse.cc)
- client code was rewritten to use new authorization algorithm
(mysql_real_connect, myslq_authenticate in sql-common/client.c)
- now server barks on 45-byte-length 4.1.0 passwords and refuses 4.1.0-style
authentification. Users with 4.1.0 passwords are blocked (sql/sql_acl.cc)
- mysqladmin.c was fixed to work correctly with new passwords
Tests for 4.0-4.1.1, 4.1.1-4.1.1 (with or without db/password) logons was performed;
mysqladmin also was tested. Additional check are nevertheless necessary.
BitKeeper/etc/ignore:
Added start_mysqld.sh mysys/main.cc to the ignore list
client/mysqladmin.c:
fixed with new password api
include/mysql.h:
So as scramble_323 accepts only null-terminated message, two scramble buffs are necessary.
gotta be fixed
include/mysql_com.h:
new constants and password.c api changes
libmysql/libmysql.c:
mysql_change_user rewritten to work with new password api
scripts/mysql_create_system_tables.sh:
fixed 'Password' column length to 41
scripts/mysql_fix_privilege_tables.sql:
fixed 'Password' column length to 41
sql-common/client.c:
mysql_real_connect rewritten to support new handshake procedure
sql/item_strfunc.cc:
Item_func_password and Item_func_old_password rewritten with new password api
sql/item_strfunc.h:
bit commented, numbers replaced with #defined constants
sql/mysql_priv.h:
removed unnecessary declaration as now all constants defined is in mysql_com.h
sql/mysqld.cc:
scramble initialization moved to sql_parce.cc:check_connection
sql/password.c:
All 4.1 functions were rolled back to 4.0 with attempt to save all possible 4.0-4.1 changes.
Names for 4.0 functions were suffixed with '_323'
Functions for new handshake were added.
sql/slave.cc:
Fixed to new constant; Bug #766 remains to be fixed
sql/slave.h:
fixed to new constant; Buf #766 remains to be fixed
sql/sql_acl.cc:
rewritten to support new passwords (41 byte-long) and password api
sql/sql_acl.h:
ditto
sql/sql_class.cc:
initialization for new members added
sql/sql_class.h:
same thing as in struct mysql - scramble is used for new family of functions, scramble_323 - for old
sql/sql_parse.cc:
check_connections was renamed to check_connection as this name reflects better what this function does
authorization part of check_connection was rewritten
check_user was rewritten with new password and acl api
new function 'authenticate', which optionally re-request scramble from client was added
fixed some typos
COM_CHANGE_USER piece of dipsatch_command() was rewritten
sql/sql_repl.h:
HASH_PASSWORD_LENGTH replaced with SCRAMBLED_PASSWORD_CHAR_LENGTH
bug #766 remains
sql/sql_yacc.yy:
Two-argument form of PASSWORD() was removed
PASSWORD() function was fixed with new password api.
BitKeeper/etc/logging_ok:
Logging to logging@openlogging.org accepted
Diffstat (limited to 'sql/password.c')
-rw-r--r-- | sql/password.c | 828 |
1 files changed, 330 insertions, 498 deletions
diff --git a/sql/password.c b/sql/password.c index 257547671e5..be6514d89c6 100644 --- a/sql/password.c +++ b/sql/password.c @@ -29,28 +29,33 @@ The password is saved (in user.password) by using the PASSWORD() function in mysql. + This is .c file because it's used in libmysqlclient, which is entirely in C. + (we need it to be portable to a variety of systems). Example: update user set password=PASSWORD("hello") where user="test" This saves a hashed number as a string in the password field. + The new autentication is performed in following manner: - New in MySQL 4.1 authentication works even more secure way. - At the first step client sends user name to the sever, and password if - it is empty. So in case of empty password authentication is as fast as before. - At the second stap servers sends scramble to client, which is encoded with - password stage2 hash stored in the password database as well as salt, needed - for client to build stage2 password to decrypt scramble. - Client decrypts the scramble and encrypts it once again with stage1 password. - This information is sent to server. - Server decrypts the scramble to get stage1 password and hashes it to get - stage2 hash. This hash is when compared to hash stored in the database. + SERVER: public_seed=create_random_string() + send(public_seed) - This authentication needs 2 packet round trips instead of one but it is much - stronger. Now if one will steal mysql database content he will not be able - to break into MySQL. + CLIENT: recv(public_seed) + hash_stage1=sha1("password") + hash_stage2=sha1(hash_stage1) + reply=xor(hash_stage1, sha1(public_seed,hash_stage2) - New Password handling functions by Peter Zaitsev + // this three steps are done in scramble() + send(reply) + + + SERVER: recv(reply) + hash_stage1=xor(reply, sha1(public_seed,hash_stage2)) + candidate_hash2=sha1(hash_stage1) + check(candidate_hash2==hash_stage2) + + // this three steps are done in check_scramble() *****************************************************************************/ @@ -60,31 +65,21 @@ #include <sha1.h> #include "mysql.h" - - -/* Character to use as version identifier for version 4.1 */ -#define PVERSION41_CHAR '*' - -/* Scramble length for new password version */ - +/************ MySQL 3.23-4.0 authentification routines: untouched ***********/ /* New (MySQL 3.21+) random generation structure initialization - SYNOPSIS randominit() rand_st OUT Structure to initialize seed1 IN First initialization parameter seed2 IN Second initialization parameter - - RETURN - none */ -void randominit(struct rand_struct *rand_st,ulong seed1, ulong seed2) -{ /* For mysql 3.21.# */ +void randominit(struct rand_struct *rand_st, ulong seed1, ulong seed2) +{ /* For mysql 3.21.# */ #ifdef HAVE_purify - bzero((char*) rand_st,sizeof(*rand_st)); /* Avoid UMC varnings */ + bzero((char*) rand_st,sizeof(*rand_st)); /* Avoid UMC varnings */ #endif rand_st->max_value= 0x3FFFFFFFL; rand_st->max_value_dbl=(double) rand_st->max_value; @@ -95,18 +90,15 @@ void randominit(struct rand_struct *rand_st,ulong seed1, ulong seed2) /* Old (MySQL 3.20) random generation structure initialization - + XXX: is to be deleted very soon! SYNOPSIS old_randominit() rand_st OUT Structure to initialize seed1 IN First initialization parameter - - RETURN - none */ -static void old_randominit(struct rand_struct *rand_st,ulong seed1) -{ /* For mysql 3.20.# */ +static void old_randominit(struct rand_struct *rand_st, ulong seed1) +{ /* For mysql 3.20.# */ rand_st->max_value= 0x01FFFFFFL; rand_st->max_value_dbl=(double) rand_st->max_value; seed1%=rand_st->max_value; @@ -115,14 +107,12 @@ static void old_randominit(struct rand_struct *rand_st,ulong seed1) /* - Generate Random number - + Generate random number. SYNOPSIS my_rnd() rand_st INOUT Structure used for number generation - - RETURN - Generated pseudo random number + RETURN VALUE + generated pseudo random number */ double my_rnd(struct rand_struct *rand_st) @@ -134,63 +124,12 @@ double my_rnd(struct rand_struct *rand_st) /* - Generate String of printable random characters of requested length - String will not be zero terminated. - + Generate binary hash from raw text string + Used for Pre-4.1 password handling SYNOPSIS - create_random_string() - length IN Lenght of - rand_st INOUT Structure used for number generation - target OUT Buffer for generation - - RETURN - none -*/ - -void create_random_string(int length,struct rand_struct *rand_st,char *target) -{ - char *end=target+length; - /* Use pointer arithmetics as it is faster way to do so. */ - for (; target<end ; target++) - *target= (char) (my_rnd(rand_st)*94+33); -} - - -/* - Encrypt/Decrypt function used for password encryption in authentication - Simple XOR is used here but it is OK as we crypt random strings - - SYNOPSIS - password_crypt() - from IN Data for encryption - to OUT Encrypt data to the buffer (may be the same) - password IN Password used for encryption (same length) - length IN Length of data to encrypt - - RETURN - none -*/ - -void password_crypt(const char *from,char *to, const char *password,int length) -{ - const char *from_end=from+length; - - while (from < from_end) - *to++= *(from++) ^* (password++); -} - - -/* - Generate binary hash from raw text password - Used for Pre-4.1 Password handling - - SYNOPSIS - hash_pasword() - result OUT Store hash in this location - password IN Plain text password to build hash - - RETURN - none + hash_password() + result OUT store hash in this location + password IN plain text password to build hash */ void hash_password(ulong *result, const char *password) @@ -200,7 +139,7 @@ void hash_password(ulong *result, const char *password) for (; *password ; password++) { if (*password == ' ' || *password == '\t') - continue; /* skipp space in password */ + continue; /* skip space in password */ tmp= (ulong) (uchar) *password; nr^= (((nr & 63)+add)*tmp)+ (nr << 8); nr2+=(nr2 << 8) ^ nr; @@ -213,514 +152,407 @@ void hash_password(ulong *result, const char *password) /* - Stage one password hashing. - Used in MySQL 4.1 password handling - + Create password to be stored in user database from raw string + Used for pre-4.1 password handling SYNOPSIS - password_hash_stage1() - to OUT Store stage one hash to this location - password IN Plain text password to build hash - - RETURN - none + make_scrambled_password_323() + to OUT store scrambled password here + password IN user-supplied password */ -void password_hash_stage1(char *to, const char *password) +void make_scrambled_password_323(char *to, const char *password) { - SHA1_CONTEXT context; - sha1_reset(&context); - for (; *password ; password++) - { - if (*password == ' ' || *password == '\t') - continue;/* skip space in password */ - sha1_input(&context,(uint8*) &password[0],1); - } - sha1_result(&context,(uint8*)to); + ulong hash_res[2]; + hash_password(hash_res, password); + sprintf(to, "%08lx%08lx", hash_res[0], hash_res[1]); } /* - Stage two password hashing. - Used in MySQL 4.1 password handling - + Scramble string with password. + Used in pre 4.1 authentication phase. SYNOPSIS - password_hash_stage2() - to INOUT Use this as stage one hash and store stage two hash here - salt IN Salt used for stage two hashing - + scramble_323() + to OUT Store scrambled message here. Buffer must be at least + SCRAMBLE_LENGTH_323+1 bytes long + message IN Message to scramble. Message must be exactly + SRAMBLE_LENGTH_323 long and NULL terminated. + password IN Password to use while scrambling + old_ver IN Force old version random number generator RETURN - none + End of scrambled string */ -void password_hash_stage2(char *to, const char *salt) +char *scramble_323(char *to, const char *message, const char *password, + my_bool old_ver) { - SHA1_CONTEXT context; - sha1_reset(&context); - sha1_input(&context,(uint8*) salt, 4); - sha1_input(&context,(uint8*) to, SHA1_HASH_SIZE); - sha1_result(&context,(uint8*) to); + struct rand_struct rand_st; + ulong hash_pass[2], hash_message[2]; + + if (password && password[0]) + { + char *to_start=to; + hash_password(hash_pass,password); + hash_password(hash_message, message); + if (old_ver) + old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]); + else + randominit(&rand_st,hash_pass[0] ^ hash_message[0], + hash_pass[1] ^ hash_message[1]); + while (*message++) + *to++= (char) (floor(my_rnd(&rand_st)*31)+64); + if (!old_ver) + { /* Make it harder to break */ + char extra=(char) (floor(my_rnd(&rand_st)*31)); + while (to_start != to) + *(to_start++)^=extra; + } + } + *to=0; + return to; } /* - Create password to be stored in user database from raw string - Handles both MySQL 4.1 and Pre-MySQL 4.1 passwords - + Check scrambled message + Used in pre 4.1 password handling SYNOPSIS - make_scramble_password() - to OUT Store scrambled password here - password IN Raw string password - force_old_scramle - IN Force generation of old scramble variant - rand_st INOUT Structure for temporary number generation. - RETURN - none + check_scramble_323() + scrambled IN scrambled message to check. + message IN original random message which was used for scrambling; must + be exactly SCRAMBLED_LENGTH_323 bytes long and + NULL-terminated. + hash_pass IN password which should be used for scrambling + old_ver IN force old (3.20) version random number generator + RETURN VALUE + 0 - password correct + !0 - password invalid */ -void make_scrambled_password(char *to,const char *password, - my_bool force_old_scramble, - struct rand_struct *rand_st) +my_bool +check_scramble_323(const char *scrambled, const char *message, + ulong *hash_pass, my_bool old_ver) { - ulong hash_res[2]; /* Used for pre 4.1 password hashing */ - unsigned short salt; /* Salt for 4.1 version password */ - uint8 digest[SHA1_HASH_SIZE]; - if (force_old_scramble) /* Pre 4.1 password encryption */ - { - hash_password(hash_res,password); - sprintf(to,"%08lx%08lx",hash_res[0],hash_res[1]); - } - else /* New password 4.1 password scrambling */ + struct rand_struct rand_st; + ulong hash_message[2]; + char buff[16],*to,extra; /* Big enough for check */ + const char *pos; + + /* Check if this exactly N bytes. Overwise this is something fishy */ + if (strlen(message) != SCRAMBLE_LENGTH_323) + return 1; /* Wrong password */ + + hash_password(hash_message,message); + if (old_ver) + old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]); + else + randominit(&rand_st,hash_pass[0] ^ hash_message[0], + hash_pass[1] ^ hash_message[1]); + to=buff; + for (pos=scrambled ; *pos ; pos++) + *to++=(char) (floor(my_rnd(&rand_st)*31)+64); + if (old_ver) + extra=0; + else + extra=(char) (floor(my_rnd(&rand_st)*31)); + to=buff; + while (*scrambled) { - to[0]=PVERSION41_CHAR; /* New passwords have version prefix */ - /* Rnd returns number from 0 to 1 so this would be good salt generation.*/ - salt=(unsigned short) (my_rnd(rand_st)*65535+1); - /* Use only 2 first bytes from it */ - sprintf(to+1,"%04x",salt); - /* First hasing is done without salt */ - password_hash_stage1((char*) digest, password); - /* Second stage is done with salt */ - password_hash_stage2((char*) digest,(char*)to+1), - /* Print resulting hash into the password*/ - sprintf(to+5, - "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6], - digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],digest[13], - digest[14],digest[15],digest[16],digest[17],digest[18],digest[19]); + if (*scrambled++ != (char) (*to++ ^ extra)) + return 1; /* Wrong password */ } + return 0; +} + +static uint8 char_val(uint8 X) +{ + return (uint) (X >= '0' && X <= '9' ? X-'0' : + X >= 'A' && X <= 'Z' ? X-'A'+10 : X-'a'+10); } /* - Convert password from binary string form to salt form - Used for MySQL 4.1 password handling - + Convert password from hex string (as stored in mysql.user) to binary form. SYNOPSIS - get_salt_from_bin_password() - res OUT Store salt form password here - password IN Binary password to be converted - salt IN hashing-salt to be used for salt form generation - - RETURN - none + get_salt_from_password_323() + res OUT store salt here + password IN password string as stored in mysql.user + NOTE + This function does not have length check for passwords. It will just crash + Password hashes in old format must have length divisible by 8 */ -void get_salt_from_bin_password(ulong *res,unsigned char *password,ulong salt) +void get_salt_from_password_323(ulong *res, const char *password) { - unsigned char *password_end=password+SCRAMBLE41_LENGTH; - *res=salt; - res++; - - /* Process password of known length*/ - while (password<password_end) + res[0]= res[1]= 0; + if (password) { - ulong val=0; - uint i; - for (i=0 ; i < 4 ; i++) - val=(val << 8)+(*password++); - *res++=val; + while (*password) + { + ulong val=0; + uint i; + for (i=0 ; i < 8 ; i++) + val=(val << 4)+char_val(*password++); + *res++=val; + } } } /* - Validate password for MySQL 4.1 password handling. - + Convert scrambled password from binary form to asciiz hex string. SYNOPSIS - validate_password() - password IN Encrypted Scramble which we got from the client - message IN Original scramble which we have sent to the client before - salt IN Password in the salted form to match to - - RETURN - 0 for correct password - !0 for invalid password + make_password_from_salt_323() + to OUT store resulting string password here, at least 17 bytes + salt IN password in salt format, 2 ulongs */ -my_bool validate_password(const char *password, const char *message, - ulong *salt) +void make_password_from_salt_323(char *to, const ulong *salt) { - char buffer[SCRAMBLE41_LENGTH]; /* Used for password validation */ - char tmpsalt[8]; /* Temporary value to convert salt to string form */ - ulong salt_candidate[6]; /* Computed candidate salt */ - ulong *sc=salt_candidate; /* we need to be able to increment */ - ulong *salt_end; - - /* Now we shall get stage1 encrypted password in buffer*/ - password_crypt(password,buffer,message,SCRAMBLE41_LENGTH); - - /* For compatibility reasons we use ulong to store salt while we need char */ - sprintf(tmpsalt,"%04x",(unsigned short)salt[0]); - - password_hash_stage2(buffer,tmpsalt); - /* Convert password to salt to compare */ - get_salt_from_bin_password(salt_candidate,(uchar*) buffer,salt[0]); - - /* Now we shall get exactly the same password as we have stored for user */ - for (salt_end=salt+5 ; salt < salt_end; ) - if (*++salt != *++sc) - return 1; - - /* Or password correct*/ - return 0; + sprintf(to,"%08lx%08lx", salt[0], salt[1]); } +/******************* MySQL 4.1.1 authentification routines ******************/ /* - Get length of password string which is stored in mysql.user table - + Generate string of printable random characters of requested length SYNOPSIS - get_password_length() - force_old_scramble IN If we wish to use pre 4.1 scramble format - - RETURN - password length >0 + create_random_string() + to OUT buffer for generation; must be at least length+1 bytes + long; result string is always null-terminated + length IN how many random characters to put in buffer + rand_st INOUT structure used for number generation */ -int get_password_length(my_bool force_old_scramble) +void create_random_string(char *to, uint length, struct rand_struct *rand_st) { - return (force_old_scramble) ? 16 : SHA1_HASH_SIZE*2+4+1; + char *end= to + length; + /* Use pointer arithmetics as it is faster way to do so. */ + for (; to < end; to++) + *to= (char) (my_rnd(rand_st)*94+33); + *to= '\0'; } -/* - Get version of the password based on mysql.user password string - - SYNOPSIS - get_password_version() - password IN Password string as stored in mysql.user - - RETURN - 0 for pre 4.1 passwords - !0 password version char for newer passwords -*/ +/* Character to use as version identifier for version 4.1 */ -char get_password_version(const char *password) -{ - if (password==NULL) return 0; - if (password[0]==PVERSION41_CHAR) return PVERSION41_CHAR; - return 0; -} +#define PVERSION41_CHAR '*' /* - Get integer value of Hex character - + Convert given octet sequence to asciiz string of hex characters; + str..str+len and 'to' may not overlap. SYNOPSIS - char_val() - X IN Character to find value for - - RETURN - Appropriate integer value + octet2hex() + buf OUT output buffer. Must be at least 2*len+1 bytes + str, len IN the beginning and the length of the input string */ - - -static inline unsigned int char_val(char X) +static +void +octet2hex(char *to, const uint8 *str, uint len) { - return (uint) (X >= '0' && X <= '9' ? X-'0' : - X >= 'A' && X <= 'Z' ? X-'A'+10 : - X-'a'+10); + static const char alphabet[] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + const uint8 *str_end= str + len; + for (; str != str_end; ++str) + { + *to++= alphabet[(*str & 0xF0) >> 4]; + *to++= alphabet[*str & 0x0F]; + } + *to++= '\0'; } /* - Get Binary salt from password as in mysql.user format - + Convert given asciiz string of hex (0..9 a..f) characters to octet + sequence. SYNOPSIS - get_salt_from_password() - res OUT Store binary salt here - password IN Password string as stored in mysql.user - - RETURN - none - - NOTE - This function does not have length check for passwords. It will just crash - Password hashes in old format must have length divisible by 8 -*/ - -void get_salt_from_password(ulong *res,const char *password) + hex2octet() + to OUT buffer to place result; must be at least len/2 bytes + str, len IN begin, length for character string; str and to may not + overlap; len % 2 == 0 +*/ + +static +void +hex2octet(uint8 *to, const char *str, uint len) { - if (password) /* zero salt corresponds to empty password */ + const char *str_end= str + len; + while (str < str_end) { - if (password[0]==PVERSION41_CHAR) /* if new password */ - { - uint val=0; - uint i; - password++; /* skip version identifier */ - - /*get hashing salt from password and store in in the start of array */ - for (i=0 ; i < 4 ; i++) - val=(val << 4)+char_val(*password++); - *res++=val; - } - /* We process old passwords the same way as new ones in other case */ -#ifdef EXTRA_DEBUG - if (strlen(password)%8!=0) - fprintf(stderr,"Warning: Incorrect password length for salting: %d\n", - strlen(password)); -#endif - while (*password) - { - ulong val=0; - uint i; - for (i=0 ; i < 8 ; i++) - val=(val << 4)+char_val(*password++); - *res++=val; - } + *to= char_val(*str++) << 4; + *to++|= char_val(*str++); } - return; } /* - Get string version as stored in mysql.user from salt form - + Encrypt/Decrypt function used for password encryption in authentication. + Simple XOR is used here but it is OK as we crypt random strings. Note, + that XOR(s1, XOR(s1, s2)) == s2, XOR(s1, s2) == XOR(s2, s1) SYNOPSIS - make_password_from_salt() - to OUT Store resulting string password here - hash_res IN Password in salt format - password_version - IN According to which version salt should be treated - - RETURN - none + my_crypt() + to OUT buffer to hold crypted string; must be at least len bytes + long; to and s1 (or s2) may be the same. + s1, s2 IN input strings (of equal length) + len IN length of s1 and s2 */ -void make_password_from_salt(char *to, ulong *hash_res,uint8 password_version) +static +void +my_crypt(char *to, const uint8 *s1, const uint8 *s2, uint len) { - if (!password_version) /* Handling of old passwords. */ - sprintf(to,"%08lx%08lx",hash_res[0],hash_res[1]); - else - if (password_version==PVERSION41_CHAR) - sprintf(to,"%c%04x%08lx%08lx%08lx%08lx%08lx",PVERSION41_CHAR,(unsigned short)hash_res[0],hash_res[1], - hash_res[2],hash_res[3],hash_res[4],hash_res[5]); - else /* Just use empty password if we can't handle it. This should not happen */ - to[0]='\0'; + const uint8 *s1_end= s1 + len; + while (s1 < s1_end) + *to++= *s1++ ^ *s2++; } /* - Convert password in salted form to binary string password and hash-salt - For old password this involes one more hashing - + MySQL 4.1.1 password hashing: SHA conversion (see RFC 2289, 3174) twice + applied to the password string, and then produced octet sequence is + converted to hex string. + The result of this function is used as return value from PASSWORD() and + is stored in the database. SYNOPSIS - get_hash_and_password() - salt IN Salt to convert from - pversion IN Password version to use - hash OUT Store zero ended hash here - bin_password OUT Store binary password here (no zero at the end) - - RETURN - 0 for pre 4.1 passwords - !0 password version char for newer passwords + make_scrambled_password() + buf OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string + password IN NULL-terminated password string */ -void get_hash_and_password(ulong *salt, uint8 pversion, char *hash, - unsigned char *bin_password) +void +make_scrambled_password(char *to, const char *password) { - int t; - ulong* salt_end; - ulong val; - SHA1_CONTEXT context; - - if (pversion) /* New password version assumed */ - { - salt_end=salt+5; - sprintf(hash,"%04x",(unsigned short)salt[0]); - while (salt<salt_end) - { - val=*(++salt); - for (t=3; t>=0; t--) - { - bin_password[t]= (char) (val & 255); - val>>=8; /* Scroll 8 bits to get next part*/ - } - bin_password+=4; /* Get to next 4 chars*/ - } - } - else - { - unsigned char *bp= bin_password; /* Binary password loop pointer */ - - /* Use zero starting hash as an indication of old password */ - hash[0]=0; - salt_end=salt+2; - /* Encode salt using SHA1 here */ - sha1_reset(&context); - while (salt<salt_end) /* Iterate over these elements*/ - { - val= *salt; - for (t=3;t>=0;t--) - { - bp[t]= (uchar) (val & 255); - val>>=8; /* Scroll 8 bits to get next part*/ - } - bp+= 4; /* Get to next 4 chars*/ - salt++; - } - /* Use 8 bytes of binary password for hash */ - sha1_input(&context,(uint8*)bin_password,8); - sha1_result(&context,(uint8*)bin_password); - } + SHA1_CONTEXT sha1_context; + uint8 hash_stage2[SHA1_HASH_SIZE]; + + sha1_reset(&sha1_context); + /* stage 1: hash password */ + sha1_input(&sha1_context, (uint8 *) password, strlen(password)); + sha1_result(&sha1_context, (uint8 *) to); + /* stage 2: hash stage1 output */ + sha1_reset(&sha1_context); + sha1_input(&sha1_context, (uint8 *) to, SHA1_HASH_SIZE); + /* separate buffer is used to pass 'to' in octet2hex */ + sha1_result(&sha1_context, hash_stage2); + /* convert hash_stage2 to hex string */ + *to++= PVERSION41_CHAR; + octet2hex(to, hash_stage2, SHA1_HASH_SIZE); } - + /* - Create key from old password to decode scramble - Used in 4.1 authentication with passwords stored old way - + Produce an obscure octet sequence from password and random + string, recieved from the server. This sequence corresponds to the + password, but password can not be easily restored from it. The sequence + is then sent to the server for validation. Trailing zero is stored in + the buf. + This function is used by client to create authenticated reply to the + server's greeting. SYNOPSIS - create_key_from_old_password() - passwd IN Password used for key generation - key OUT Created 20 bytes key - - RETURN - None + scramble() + buf OUT store scrambled string here. The buf must be at least + SHA1_HASH_SIZE+1 bytes long. + message IN random message, must be exactly SCRAMBLE_LENGTH long and + NULL-terminated. + password IN users' password + RETURN VALUE + end of scrambled string */ - -void create_key_from_old_password(const char *passwd, char *key) +char * +scramble(char *to, const char *message, const char *password) { - char buffer[SCRAMBLE41_LENGTH]; /* Buffer for various needs */ - ulong salt[6]; /* Salt (large for safety) */ - /* At first hash password to the string stored in password */ - make_scrambled_password(buffer,passwd,1,(struct rand_struct *)NULL); - /* Now convert it to the salt form */ - get_salt_from_password(salt,buffer); - /* Finally get hash and bin password from salt */ - get_hash_and_password(salt,0,buffer,(unsigned char*) key); + SHA1_CONTEXT sha1_context; + uint8 hash_stage1[SHA1_HASH_SIZE]; + uint8 hash_stage2[SHA1_HASH_SIZE]; + + sha1_reset(&sha1_context); + /* stage 1: hash password */ + sha1_input(&sha1_context, (uint8 *) password, strlen(password)); + sha1_result(&sha1_context, hash_stage1); + /* stage 2: hash stage 1; note that hash_stage2 is stored in the database */ + sha1_reset(&sha1_context); + sha1_input(&sha1_context, hash_stage1, SHA1_HASH_SIZE); + sha1_result(&sha1_context, hash_stage2); + /* create crypt string as sha1(message, hash_stage2) */; + sha1_reset(&sha1_context); + sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH); + sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE); + /* xor allows 'from' and 'to' overlap: lets take advantage of it */ + sha1_result(&sha1_context, (uint8 *) to); + my_crypt(to, (const uint8 *) to, hash_stage1, SCRAMBLE_LENGTH); + to[SHA1_HASH_SIZE]= '\0'; + return to + SHA1_HASH_SIZE; } /* - Scramble string with password - Used at pre 4.1 authentication phase. - + Check that scrambled message corresponds to the password; the function + is used by server to check that recieved reply is authentic. + This function does not check lengths of given strings: message must be + null-terminated, reply and hash_stage2 must be at least SHA1_HASH_SIZE + long (if not, something fishy is going on). SYNOPSIS - scramble() - to OUT Store scrambled message here - message IN Message to scramble - password IN Password to use while scrambling - old_ver IN Forse old version random number generator - - RETURN - End of scrambled string + check_scramble() + scramble IN clients' reply, presumably produced by scramble() + message IN original random string, previously sent to client + (presumably second argument of scramble()), must be + exactly SCRAMBLE_LENGTH long and NULL-terminated. + hash_stage2 IN hex2octet-decoded database entry + RETURN VALUE + 0 password is correct + !0 password is invalid */ -char *scramble(char *to,const char *message,const char *password, - my_bool old_ver) +my_bool +check_scramble(const char *scramble, const char *message, + const uint8 *hash_stage2) { - struct rand_struct rand_st; - ulong hash_pass[2],hash_message[2]; - char message_buffer[9]; /* Real message buffer */ - char *msg=message_buffer; - - /* We use special message buffer now as new server can provide longer hash */ - - memcpy(message_buffer,message,8); - message_buffer[8]=0; - - if (password && password[0]) - { - char *to_start=to; - hash_password(hash_pass,password); - hash_password(hash_message,message_buffer); - if (old_ver) - old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]); - else - randominit(&rand_st,hash_pass[0] ^ hash_message[0], - hash_pass[1] ^ hash_message[1]); - while (*msg++) - *to++= (char) (floor(my_rnd(&rand_st)*31)+64); - if (!old_ver) - { /* Make it harder to break */ - char extra=(char) (floor(my_rnd(&rand_st)*31)); - while (to_start != to) - *(to_start++)^=extra; - } - } - *to=0; - return to; + SHA1_CONTEXT sha1_context; + uint8 buf[SHA1_HASH_SIZE]; + uint8 hash_stage2_reassured[SHA1_HASH_SIZE]; + + sha1_reset(&sha1_context); + /* create key to encrypt scramble */ + sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH); + sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE); + sha1_result(&sha1_context, buf); + /* encrypt scramble */ + my_crypt((char *) buf, buf, (const uint8 *) scramble, SCRAMBLE_LENGTH); + /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */ + sha1_reset(&sha1_context); + sha1_input(&sha1_context, buf, SHA1_HASH_SIZE); + sha1_result(&sha1_context, hash_stage2_reassured); + return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE); } /* - Check scrambled message - Used for pre 4.1 password handling - + Convert scrambled password from asciiz hex string to binary form. SYNOPSIS - scramble() - scrambled IN Scrambled message to check - message IN Original message which was scramble - hash_pass IN Password which should be used for scrambling - old_ver IN Forse old version random number generator + get_salt_from_password() + res OUT buf to hold password. Must be at least SHA1_HASH_SIZE + bytes long. + password IN 4.1.1 version value of user.password +*/ + +void get_salt_from_password(uint8 *hash_stage2, const char *password) +{ + hex2octet(hash_stage2, password+1 /* skip '*' */, SHA1_HASH_SIZE * 2); +} - RETURN - 0 Password correct - !0 Password invalid +/* + Convert scrambled password from binary form to asciiz hex string. + SYNOPSIS + make_password_from_salt() + to OUT store resulting string here, 2*SHA1_HASH_SIZE+2 bytes + salt IN password in salt format */ -my_bool check_scramble(const char *scrambled, const char *message, - ulong *hash_pass, my_bool old_ver) +void make_password_from_salt(char *to, const uint8 *hash_stage2) { - struct rand_struct rand_st; - ulong hash_message[2]; - char buff[16],*to,extra; /* Big enough for check */ - const char *pos; - char message_buffer[SCRAMBLE_LENGTH+1]; /* Copy of message */ - - /* We need to copy the message as this function can be called for MySQL 4.1 - scramble which is not zero ended and can have zeroes inside - We could just write zero to proper place in original message but - this would make it harder to understand code for next generations - */ - - memcpy(message_buffer,message,SCRAMBLE_LENGTH); /* Ignore the rest */ - message_buffer[SCRAMBLE_LENGTH]=0; - - /* Check if this exactly N bytes. Overwise this is something fishy */ - if (strlen(message_buffer)!=SCRAMBLE_LENGTH) - return 1; /* Wrong password */ - - hash_password(hash_message,message_buffer); - if (old_ver) - old_randominit(&rand_st,hash_pass[0] ^ hash_message[0]); - else - randominit(&rand_st,hash_pass[0] ^ hash_message[0], - hash_pass[1] ^ hash_message[1]); - to=buff; - for (pos=scrambled ; *pos ; pos++) - *to++=(char) (floor(my_rnd(&rand_st)*31)+64); - if (old_ver) - extra=0; - else - extra=(char) (floor(my_rnd(&rand_st)*31)); - to=buff; - while (*scrambled) - { - if (*scrambled++ != (char) (*to++ ^ extra)) - return 1; /* Wrong password */ - } - return 0; + *to++= PVERSION41_CHAR; + octet2hex(to, hash_stage2, SHA1_HASH_SIZE); } |