From 947a64c05f389456a315a4a372fac0fd9f5b78fb Mon Sep 17 00:00:00 2001 From: CyanWorlds Date: Thu, 13 Apr 2017 13:42:50 +0000 Subject: [PATCH 1/2] Changed password hashing to try SHA-1 first and if that doesn't work then try SHA-0. This should allow shard owners to chose either SHA-1 or SHA-0 for their password hash without interfering with other shards. And allow them to change their password hash without requiring everyone to reset their passwords. This also allows for future expansion of new hash techniques to be added, of course the more hash techniques added the more delay at login for the lowest on the totem pole. I have tested this against the MOULa shard that uses SHA-0 and it works, there is a slight flash of the "Connecting dialog" as it re-tries sending the login but if the user saves the password, there is no flash and no delay. --- .../Sources/Plasma/Apps/plClient/winmain.cpp | 111 +++++++++++++----- 1 file changed, 80 insertions(+), 31 deletions(-) diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/winmain.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/winmain.cpp index 608dde6c..aba61bf0 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/winmain.cpp +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/winmain.cpp @@ -206,7 +206,7 @@ bool GetDisksProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc); void GetOldCryptKey(UInt32* cryptKey, unsigned size); void GetCryptKey(UInt32* cryptKey, unsigned size); static void SaveUserPass (char *username, char *password, ShaDigest *pNamePassHash, bool remember_password, - bool fromGT); + int whichHash); static void LoadUserPass (const wchar *accountName, char *username, ShaDigest *pNamePassHash, bool *pRemember, bool fromGT, int *pFocus); static void AuthFailedStrings (ENetError authError, bool fromGT, @@ -322,11 +322,25 @@ static bool TGRunLoginDialog (const wchar *accountName, bool fromGT) if (Remember[0] == 'y') bRemember = true; - SaveUserPass (Username, Password, &NamePassHash, bRemember, fromGT); - - // Do login & see if it failed + // cycle through the hash types until we find one that matches or errors out + int whichHash = 1; ENetError auth; - bool cancelled = AuthenticateNetClientComm(&auth, NULL); + bool cancelled; + while (whichHash >= 0 ) + { + SaveUserPass (Username, Password, &NamePassHash, bRemember, whichHash); + + // Do login & see if it failed + cancelled = AuthenticateNetClientComm(&auth, NULL); + // if the password was successful then go to the end processing + if (IS_NET_SUCCESS(auth) && !cancelled) + break; + // if it was cancelled or any error other than wrong password then go to end processing + if (cancelled || auth != kNetErrAuthenticationFailed) + break; + // otherwise try then next Hash type + whichHash--; + } if (IS_NET_SUCCESS (auth) && !cancelled) break; @@ -1081,7 +1095,7 @@ BOOL CALLBACK UruTOSDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM l } static void SaveUserPass (char *username, char *password, ShaDigest *pNamePassHash, bool remember_password, - bool fromGT) + int whichHash) { UInt32 cryptKey[4]; ZeroMemory(cryptKey, sizeof(cryptKey)); @@ -1097,7 +1111,30 @@ static void SaveUserPass (char *username, char *password, ShaDigest *pNamePassHa if (StrCmp(password, FAKE_PASS_STRING) != 0) { StrToUnicode(wpassword, password, arrsize(wpassword)); - CryptHashPassword(wusername, wpassword, pNamePassHash); + + switch( whichHash ) + { + case 1: + CryptDigest( + kCryptSha1, + pNamePassHash, + StrLen(password) * sizeof(password[0]), + password + ); + + if (IsMachineLittleEndian()) { + pNamePassHash->data[0] = ToBigEndian(pNamePassHash->data[0]); + pNamePassHash->data[1] = ToBigEndian(pNamePassHash->data[1]); + pNamePassHash->data[2] = ToBigEndian(pNamePassHash->data[2]); + pNamePassHash->data[3] = ToBigEndian(pNamePassHash->data[3]); + pNamePassHash->data[4] = ToBigEndian(pNamePassHash->data[4]); + } + break; + + default: + CryptHashPassword(wusername, wpassword, pNamePassHash); + break; + } } NetCommSetAccountUsernamePassword(wusername, *pNamePassHash); @@ -1106,28 +1143,26 @@ static void SaveUserPass (char *username, char *password, ShaDigest *pNamePassHa else NetCommSetAuthTokenAndOS(nil, L"win"); - if (!fromGT) { - wchar fileAndPath[MAX_PATH]; - PathGetInitDirectory(fileAndPath, arrsize(fileAndPath)); - PathAddFilename(fileAndPath, fileAndPath, L"login.dat", arrsize(fileAndPath)); + wchar fileAndPath[MAX_PATH]; + PathGetInitDirectory(fileAndPath, arrsize(fileAndPath)); + PathAddFilename(fileAndPath, fileAndPath, L"login.dat", arrsize(fileAndPath)); #ifndef PLASMA_EXTERNAL_RELEASE - // internal builds can use the local init directory - wchar localFileAndPath[MAX_PATH]; - StrCopy(localFileAndPath, L"init\\login.dat", arrsize(localFileAndPath)); - if (PathDoesFileExist(localFileAndPath)) - StrCopy(fileAndPath, localFileAndPath, arrsize(localFileAndPath)); + // internal builds can use the local init directory + wchar localFileAndPath[MAX_PATH]; + StrCopy(localFileAndPath, L"init\\login.dat", arrsize(localFileAndPath)); + if (PathDoesFileExist(localFileAndPath)) + StrCopy(fileAndPath, localFileAndPath, arrsize(localFileAndPath)); #endif - hsStream* stream = plEncryptedStream::OpenEncryptedFileWrite(fileAndPath, cryptKey); - if (stream) - { - stream->Write(sizeof(cryptKey), cryptKey); - stream->WriteSafeString(username); - stream->Writebool(remember_password); - if (remember_password) - stream->Write(sizeof(pNamePassHash->data), pNamePassHash->data); - stream->Close(); - delete stream; - } + hsStream* stream = plEncryptedStream::OpenEncryptedFileWrite(fileAndPath, cryptKey); + if (stream) + { + stream->Write(sizeof(cryptKey), cryptKey); + stream->WriteSafeString(username); + stream->Writebool(remember_password); + if (remember_password) + stream->Write(sizeof(pNamePassHash->data), pNamePassHash->data); + stream->Close(); + delete stream; } } @@ -1418,11 +1453,25 @@ BOOL CALLBACK UruLoginDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM GetDlgItemText(hwndDlg, IDC_URULOGIN_PASSWORD, password, kMaxPasswordLength); remember_password = (IsDlgButtonChecked(hwndDlg, IDC_URULOGIN_REMEMBERPASS) == BST_CHECKED); - SaveUserPass (username, password, &namePassHash, remember_password, loginParam->fromGT); - + // cycle through the hash types until we find one that matches or errors out + int whichHash = 1; LoginDialogParam loginParam; - MemSet(&loginParam, 0, sizeof(loginParam)); - bool cancelled = AuthenticateNetClientComm(&loginParam.authError, hwndDlg); + bool cancelled; + while (whichHash >= 0 ) + { + SaveUserPass (username, password, &namePassHash, remember_password, whichHash); + + MemSet(&loginParam, 0, sizeof(loginParam)); + cancelled = AuthenticateNetClientComm(&loginParam.authError, hwndDlg); + // if the password was successful then go to the end processing + if (IS_NET_SUCCESS(loginParam.authError) && !cancelled) + break; + // if it was cancelled or any error other than wrong password then go to end processing + if (cancelled || loginParam.authError != kNetErrAuthenticationFailed) + break; + // otherwise try then next Hash type + whichHash--; + } if (IS_NET_SUCCESS(loginParam.authError) && !cancelled) EndDialog(hwndDlg, ok); From c888e43c73a2af78f7b70e4315a3eb3786f59a28 Mon Sep 17 00:00:00 2001 From: CyanWorlds Date: Fri, 12 May 2017 11:28:11 -0700 Subject: [PATCH 2/2] Cleaned up password hashing to try SH1 then SH0. More comments and easier to modify. --- .../Sources/Plasma/Apps/plClient/winmain.cpp | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/winmain.cpp b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/winmain.cpp index aba61bf0..06f9a76d 100644 --- a/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/winmain.cpp +++ b/MOULOpenSourceClientPlugin/Plasma20/Sources/Plasma/Apps/plClient/winmain.cpp @@ -200,6 +200,18 @@ struct LoginDialogParam { wchar accountName[kMaxAccountNameLength]; }; +// List of hash styles we are going to test against the server +// ... this method of trying multiple hashes against the server has risks of being more compromised than just testing one hash. +// ... So, if you know your unique client is only going to connect to your server then it would be wise to limit the testing to one hash. +// ... Which can be done simply by setting FIRST_PASSWORD_HASH and LAST_PASSWORD_HASH to the hash that you use. +enum +{ + kPasswordHashSHA0, + kPasswordHashSHA1 +}; +static const int FIRST_PASSWORD_HASH = kPasswordHashSHA1; +static const int LAST_PASSWORD_HASH = kPasswordHashSHA0; + bool AuthenticateNetClientComm(ENetError* result, HWND parentWnd); bool IsExpired(); bool GetDisksProperty(HANDLE hDevice, PSTORAGE_DEVICE_DESCRIPTOR pDevDesc); @@ -323,10 +335,9 @@ static bool TGRunLoginDialog (const wchar *accountName, bool fromGT) bRemember = true; // cycle through the hash types until we find one that matches or errors out - int whichHash = 1; ENetError auth; bool cancelled; - while (whichHash >= 0 ) + for (int whichHash=FIRST_PASSWORD_HASH; whichHash >= LAST_PASSWORD_HASH; whichHash-- ) { SaveUserPass (Username, Password, &NamePassHash, bRemember, whichHash); @@ -338,8 +349,6 @@ static bool TGRunLoginDialog (const wchar *accountName, bool fromGT) // if it was cancelled or any error other than wrong password then go to end processing if (cancelled || auth != kNetErrAuthenticationFailed) break; - // otherwise try then next Hash type - whichHash--; } if (IS_NET_SUCCESS (auth) && !cancelled) @@ -947,18 +956,6 @@ void DebugMsgF(const char* format, ...) #endif } -static bool IsMachineLittleEndian() { - int i = 1; - char *p = (char *) &i; - if (p[0] == 1) // Lowest address contains the least significant byte - return true; - else - return false; -} - -inline static dword ToBigEndian (dword value) { - return ((value) << 24) | ((value & 0x0000ff00) << 8) | ((value & 0x00ff0000) >> 8) | ((value) >> 24); -} static void AuthFailedStrings (ENetError authError, bool fromGT, const char **ppStr1, const char **ppStr2, @@ -1114,7 +1111,7 @@ static void SaveUserPass (char *username, char *password, ShaDigest *pNamePassHa switch( whichHash ) { - case 1: + case kPasswordHashSHA1: CryptDigest( kCryptSha1, pNamePassHash, @@ -1122,15 +1119,16 @@ static void SaveUserPass (char *username, char *password, ShaDigest *pNamePassHa password ); - if (IsMachineLittleEndian()) { - pNamePassHash->data[0] = ToBigEndian(pNamePassHash->data[0]); - pNamePassHash->data[1] = ToBigEndian(pNamePassHash->data[1]); - pNamePassHash->data[2] = ToBigEndian(pNamePassHash->data[2]); - pNamePassHash->data[3] = ToBigEndian(pNamePassHash->data[3]); - pNamePassHash->data[4] = ToBigEndian(pNamePassHash->data[4]); - } + // switch the endianness of the hash to big endian + // NOTE: this is legacy from GameTap days to match GameTap's endianness + pNamePassHash->data[0] = hsUNSWAP32(pNamePassHash->data[0]); + pNamePassHash->data[1] = hsUNSWAP32(pNamePassHash->data[1]); + pNamePassHash->data[2] = hsUNSWAP32(pNamePassHash->data[2]); + pNamePassHash->data[3] = hsUNSWAP32(pNamePassHash->data[3]); + pNamePassHash->data[4] = hsUNSWAP32(pNamePassHash->data[4]); break; + case kPasswordHashSHA0: default: CryptHashPassword(wusername, wpassword, pNamePassHash); break; @@ -1454,10 +1452,9 @@ BOOL CALLBACK UruLoginDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM remember_password = (IsDlgButtonChecked(hwndDlg, IDC_URULOGIN_REMEMBERPASS) == BST_CHECKED); // cycle through the hash types until we find one that matches or errors out - int whichHash = 1; LoginDialogParam loginParam; bool cancelled; - while (whichHash >= 0 ) + for (int whichHash=FIRST_PASSWORD_HASH; whichHash >= LAST_PASSWORD_HASH; whichHash-- ) { SaveUserPass (username, password, &namePassHash, remember_password, whichHash); @@ -1469,8 +1466,6 @@ BOOL CALLBACK UruLoginDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM // if it was cancelled or any error other than wrong password then go to end processing if (cancelled || loginParam.authError != kNetErrAuthenticationFailed) break; - // otherwise try then next Hash type - whichHash--; } if (IS_NET_SUCCESS(loginParam.authError) && !cancelled)