From 4058ed863672f02202af6b6074e9a7896ab44f2f Mon Sep 17 00:00:00 2001 From: Nathan Wallach Date: Thu, 21 Nov 2024 12:11:46 +0200 Subject: [PATCH] Add support for wide characters in passwords. The crypt function cannot handle most wide (Unicode) characters, but does handle some by attempting to downgrade to an 8-bit string. In order to support wide characters in passwords while not breaking any passwords which worked before this change, a password will be encoded into UTF-8 before being sent to crypt only when crypt dies on the original string. --- lib/WeBWorK/Authen.pm | 11 +++++++++-- lib/WeBWorK/ContentGenerator/Options.pm | 11 ++++++++++- lib/WeBWorK/Utils.pm | 10 +++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/WeBWorK/Authen.pm b/lib/WeBWorK/Authen.pm index df77a42cfb..2dcb29e076 100644 --- a/lib/WeBWorK/Authen.pm +++ b/lib/WeBWorK/Authen.pm @@ -633,8 +633,15 @@ sub checkPassword { my $Password = $db->getPassword($userID); if (defined $Password) { # Check against the password in the database. - my $possibleCryptPassword = crypt $possibleClearPassword, $Password->password; - my $dbPassword = $Password->password; + my $possibleCryptPassword = ''; + # Wrap crypt in an eval to catch any "Wide character in crypt" errors. + # If crypt fails due to a wide character, encode to UTF-8 before calling crypt. + eval { $possibleCryptPassword = crypt $possibleClearPassword, $Password->password; }; + if ($@ && $@ =~ /Wide char/) { + $possibleCryptPassword = crypt Encode::encode_utf8($possibleClearPassword), $Password->password; + } + + my $dbPassword = $Password->password; # This next line explicitly insures that blank or null passwords from the database can never succeed in matching # an entered password. This also rejects cases when the database has a crypted password which matches a # submitted all white-space or null password by requiring that the $possibleClearPassword contain some non-space diff --git a/lib/WeBWorK/ContentGenerator/Options.pm b/lib/WeBWorK/ContentGenerator/Options.pm index 911b4c27d4..91b52dc8b7 100644 --- a/lib/WeBWorK/ContentGenerator/Options.pm +++ b/lib/WeBWorK/ContentGenerator/Options.pm @@ -56,7 +56,16 @@ sub initialize ($c) { $userID ne $effectiveUserID ? eval { $db->getPassword($c->{effectiveUser}->user_id) } : $password; # Check that either password is not defined or if it is defined then we have the right one. - if (!defined $password || crypt($currP // '', $password->password) eq $password->password) { + my $cryptedCurrP; + if (defined $password) { + # Wrap crypt in an eval to catch any "Wide character in crypt" errors. + # If crypt fails due to a wide character, encode to UTF-8 before calling crypt. + eval { $cryptedCurrP = crypt($currP // '', $password->password); }; + if ($@ && $@ =~ /Wide char/) { + $cryptedCurrP = crypt(Encode::encode_utf8($currP), $password->password); + } + } + if (!defined $password || $cryptedCurrP eq $password->password) { my $e_user_name = $c->{effectiveUser}->first_name . ' ' . $c->{effectiveUser}->last_name; if ($newP eq $confirmP) { if (!defined $effectiveUserPassword) { diff --git a/lib/WeBWorK/Utils.pm b/lib/WeBWorK/Utils.pm index 5065fe82d7..d65974bbbd 100644 --- a/lib/WeBWorK/Utils.pm +++ b/lib/WeBWorK/Utils.pm @@ -151,7 +151,15 @@ sub cryptPassword ($clearPassword) { $salt .= ('.', '/', '0' .. '9', 'A' .. 'Z', 'a' .. 'z')[ rand 64 ]; } - return crypt(trim_spaces($clearPassword), $salt); + # Wrap crypt in an eval to catch any "Wide character in crypt" errors. + # If crypt fails due to a wide character, encode to UTF-8 before calling crypt. + my $cryptedPassword = ''; + eval { $cryptedPassword = crypt(trim_spaces($clearPassword), $salt); }; + if ($@ && $@ =~ /Wide char/) { + $cryptedPassword = crypt(Encode::encode_utf8(trim_spaces($clearPassword)), $salt); + } + + return $cryptedPassword; } sub undefstr ($default, @values) {