aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorraczman <raczman@gmail.com>2013-08-25 14:02:40 +0100
committerNay <dnpd.dd@gmail.com>2013-08-25 14:02:40 +0100
commitba22baebbd1394cc69366d7a19d879da43885430 (patch)
treeef30974328e765cde2b8fea6be2bc4fca53a5bf1
parente96aa444b07eb6d9b96b37bcef7742ad96225fb4 (diff)
Core/Auth: Implement time-based token for user login as described in RFC 6238.
New column in account table is a base32 of token key bytes, coincidentally it is the same format Google's Authenticator Android app uses. If you want that to work, set system time on server correctly and use ntpd. Closes #10527 Signed-off-by: Nay <dnpd.dd@gmail.com>
-rw-r--r--sql/base/auth_database.sql1
-rw-r--r--sql/updates/auth/2013_08_25_00_auth.sql1
-rw-r--r--src/server/authserver/Authentication/TOTP.cpp94
-rw-r--r--src/server/authserver/Authentication/TOTP.h29
-rw-r--r--src/server/authserver/Server/AuthSocket.cpp26
-rw-r--r--src/server/authserver/Server/AuthSocket.h1
-rw-r--r--src/server/collision/Maps/TileAssembler.cpp2
-rw-r--r--src/server/shared/Cryptography/HMACSHA1.cpp5
-rw-r--r--src/server/shared/Cryptography/HMACSHA1.h1
-rw-r--r--src/server/shared/Database/Implementation/LoginDatabase.cpp2
10 files changed, 160 insertions, 2 deletions
diff --git a/sql/base/auth_database.sql b/sql/base/auth_database.sql
index 1f247a6b8ef..77f997a1718 100644
--- a/sql/base/auth_database.sql
+++ b/sql/base/auth_database.sql
@@ -29,6 +29,7 @@ CREATE TABLE `account` (
`sessionkey` varchar(80) NOT NULL DEFAULT '',
`v` varchar(64) NOT NULL DEFAULT '',
`s` varchar(64) NOT NULL DEFAULT '',
+ `token_key` varchar(100) NOT NULL DEFAULT '',
`email` varchar(254) NOT NULL DEFAULT '',
`joindate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_ip` varchar(15) NOT NULL DEFAULT '127.0.0.1',
diff --git a/sql/updates/auth/2013_08_25_00_auth.sql b/sql/updates/auth/2013_08_25_00_auth.sql
new file mode 100644
index 00000000000..d1abc9eb958
--- /dev/null
+++ b/sql/updates/auth/2013_08_25_00_auth.sql
@@ -0,0 +1 @@
+ALTER TABLE `account` ADD COLUMN `token_key` varchar(100) NOT NULL DEFAULT '' AFTER `s`;
diff --git a/src/server/authserver/Authentication/TOTP.cpp b/src/server/authserver/Authentication/TOTP.cpp
new file mode 100644
index 00000000000..43c97c296d9
--- /dev/null
+++ b/src/server/authserver/Authentication/TOTP.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "TOTP.h"
+
+int base32_decode(const char* encoded, char* result, int bufSize)
+{
+ // Base32 implementation
+ // Copyright 2010 Google Inc.
+ // Author: Markus Gutschke
+ // Licensed under the Apache License, Version 2.0
+ int buffer = 0;
+ int bitsLeft = 0;
+ int count = 0;
+ for (const char *ptr = encoded; count < bufSize && *ptr; ++ptr)
+ {
+ char ch = *ptr;
+ if (ch == ' ' || ch == ' ' || ch == '\r' || ch == '\n' || ch == '-')
+ continue;
+ buffer <<= 5;
+
+ // Deal with commonly mistyped characters
+ if (ch == '0')
+ ch = 'O';
+ else if (ch == '1')
+ ch = 'L';
+ else if (ch == '8')
+ ch = 'B';
+
+ // Look up one base32 digit
+ if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
+ ch = (ch & 0x1F) - 1;
+ else if (ch >= '2' && ch <= '7')
+ ch -= '2' - 26;
+ else
+ return -1;
+
+ buffer |= ch;
+ bitsLeft += 5;
+ if (bitsLeft >= 8)
+ {
+ result[count++] = buffer >> (bitsLeft - 8);
+ bitsLeft -= 8;
+ }
+ }
+
+ if (count < bufSize)
+ result[count] = '\000';
+ return count;
+}
+
+#define HMAC_RES_SIZE 20
+
+namespace TOTP
+{
+ unsigned int GenerateToken(const char* b32key)
+ {
+ size_t keySize = strlen(b32key);
+ int bufsize = (keySize + 7)/8*5;
+ char* encoded = new char[bufsize];
+ memset(encoded, 0, bufsize);
+ unsigned int hmacResSize = HMAC_RES_SIZE;
+ unsigned char hmacRes[HMAC_RES_SIZE];
+ unsigned long timestamp = time(NULL)/30;
+ unsigned char challenge[8];
+
+ for (int i = 8; i--;timestamp >>= 8)
+ challenge[i] = timestamp;
+
+ base32_decode(b32key, encoded, bufsize);
+ HMAC(EVP_sha1(), encoded, bufsize, challenge, 8, hmacRes, &hmacResSize);
+ unsigned int offset = hmacRes[19] & 0xF;
+ unsigned int truncHash = (hmacRes[offset] << 24) | (hmacRes[offset+1] << 16 )| (hmacRes[offset+2] << 8) | (hmacRes[offset+3]);
+ truncHash &= 0x7FFFFFFF;
+
+ delete[] encoded;
+
+ return truncHash % 1000000;
+ }
+}
diff --git a/src/server/authserver/Authentication/TOTP.h b/src/server/authserver/Authentication/TOTP.h
new file mode 100644
index 00000000000..3080e7c7787
--- /dev/null
+++ b/src/server/authserver/Authentication/TOTP.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TOTP_H
+#define _TOPT_H
+
+#include "openssl/hmac.h"
+#include "openssl/evp.h"
+
+namespace TOTP
+{
+ unsigned int GenerateToken(const char* b32key);
+}
+
+#endif
diff --git a/src/server/authserver/Server/AuthSocket.cpp b/src/server/authserver/Server/AuthSocket.cpp
index c2131f5dbf7..93c03e26c88 100644
--- a/src/server/authserver/Server/AuthSocket.cpp
+++ b/src/server/authserver/Server/AuthSocket.cpp
@@ -27,6 +27,7 @@
#include "RealmList.h"
#include "AuthSocket.h"
#include "AuthCodes.h"
+#include "TOTP.h"
#include "SHA1.h"
#include "openssl/crypto.h"
@@ -492,6 +493,12 @@ bool AuthSocket::_HandleLogonChallenge()
pkt.append(s.AsByteArray().get(), s.GetNumBytes()); // 32 bytes
pkt.append(unk3.AsByteArray(16).get(), 16);
uint8 securityFlags = 0;
+
+ // Check if token is used
+ _tokenKey = fields[8].GetString();
+ if (!_tokenKey.empty())
+ securityFlags = 4;
+
pkt << uint8(securityFlags); // security flags (0x0...0x04)
if (securityFlags & 0x01) // PIN input
@@ -652,6 +659,25 @@ bool AuthSocket::_HandleLogonProof()
sha.UpdateBigNumbers(&A, &M, &K, NULL);
sha.Finalize();
+ // Check auth token
+ if ((lp.securityFlags & 0x04) || !_tokenKey.empty())
+ {
+ uint8 size;
+ socket().recv((char*)&size, 1);
+ char* token = new char[size + 1];
+ token[size] = '\0';
+ socket().recv(token, size);
+ unsigned int validToken = TOTP::GenerateToken(_tokenKey.c_str());
+ unsigned int incomingToken = atoi(token);
+ delete[] token;
+ if (validToken != incomingToken)
+ {
+ char data[] = { AUTH_LOGON_PROOF, WOW_FAIL_UNKNOWN_ACCOUNT, 3, 0 };
+ socket().send(data, sizeof(data));
+ return false;
+ }
+ }
+
if (_expversion & POST_BC_EXP_FLAG) // 2.x and 3.x clients
{
sAuthLogonProof_S proof;
diff --git a/src/server/authserver/Server/AuthSocket.h b/src/server/authserver/Server/AuthSocket.h
index 6c13f85a022..c6513eaa5bf 100644
--- a/src/server/authserver/Server/AuthSocket.h
+++ b/src/server/authserver/Server/AuthSocket.h
@@ -69,6 +69,7 @@ private:
bool _authed;
std::string _login;
+ std::string _tokenKey;
// Since GetLocaleByName() is _NOT_ bijective, we have to store the locale as a string. Otherwise we can't differ
// between enUS and enGB, which is important for the patch system
diff --git a/src/server/collision/Maps/TileAssembler.cpp b/src/server/collision/Maps/TileAssembler.cpp
index c0797b0fdcc..ddaa8cb9c67 100644
--- a/src/server/collision/Maps/TileAssembler.cpp
+++ b/src/server/collision/Maps/TileAssembler.cpp
@@ -105,7 +105,7 @@ namespace VMAP
}
catch (std::exception& e)
{
- printf("Exception ""%s"" when calling pTree.build", e.what());
+ printf("Exception ""%s"" when calling pTree.build", e.what());
return false;
}
diff --git a/src/server/shared/Cryptography/HMACSHA1.cpp b/src/server/shared/Cryptography/HMACSHA1.cpp
index 62d1997ded2..c6c49f14a8e 100644
--- a/src/server/shared/Cryptography/HMACSHA1.cpp
+++ b/src/server/shared/Cryptography/HMACSHA1.cpp
@@ -36,6 +36,11 @@ void HmacHash::UpdateData(const std::string &str)
HMAC_Update(&m_ctx, (uint8 const*)str.c_str(), str.length());
}
+void HmacHash::UpdateData(const uint8* data, size_t len)
+{
+ HMAC_Update(&m_ctx, data, len);
+}
+
void HmacHash::Finalize()
{
uint32 length = 0;
diff --git a/src/server/shared/Cryptography/HMACSHA1.h b/src/server/shared/Cryptography/HMACSHA1.h
index e09e7fdb43c..04b8f7d0277 100644
--- a/src/server/shared/Cryptography/HMACSHA1.h
+++ b/src/server/shared/Cryptography/HMACSHA1.h
@@ -34,6 +34,7 @@ class HmacHash
HmacHash(uint32 len, uint8 *seed);
~HmacHash();
void UpdateData(const std::string &str);
+ void UpdateData(const uint8* data, size_t len);
void Finalize();
uint8 *ComputeHash(BigNumber* bn);
uint8 *GetDigest() { return (uint8*)m_digest; }
diff --git a/src/server/shared/Database/Implementation/LoginDatabase.cpp b/src/server/shared/Database/Implementation/LoginDatabase.cpp
index 6113dd61d70..26940c8a599 100644
--- a/src/server/shared/Database/Implementation/LoginDatabase.cpp
+++ b/src/server/shared/Database/Implementation/LoginDatabase.cpp
@@ -37,7 +37,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_SEL_SESSIONKEY, "SELECT a.sessionkey, a.id, aa.gmlevel FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_UPD_VS, "UPDATE account SET v = ?, s = ? WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_LOGONPROOF, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_ASYNC);
- PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.sha_pass_hash, a.id, a.locked, a.lock_country, a.last_ip, aa.gmlevel, a.v, a.s FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.username = ?", CONNECTION_SYNCH);
+ PrepareStatement(LOGIN_SEL_LOGONCHALLENGE, "SELECT a.sha_pass_hash, a.id, a.locked, a.lock_country, a.last_ip, aa.gmlevel, a.v, a.s, a.token_key FROM account a LEFT JOIN account_access aa ON (a.id = aa.id) WHERE a.username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_LOGON_COUNTRY, "SELECT country FROM ip2nation WHERE ip < ? ORDER BY ip DESC LIMIT 0,1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_UPD_FAILEDLOGINS, "UPDATE account SET failed_logins = failed_logins + 1 WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_FAILEDLOGINS, "SELECT id, failed_logins FROM account WHERE username = ?", CONNECTION_SYNCH);