Skip to content
This repository has been archived by the owner on Jul 27, 2020. It is now read-only.

Commit

Permalink
Core/Auth: Implement time-based token for user login as described in …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
zamalaev committed Nov 25, 2013
1 parent 2d70eb0 commit 5161634
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 1 deletion.
1 change: 1 addition & 0 deletions sql/base/auth_database_2013_11_24.sql
Expand Up @@ -17,6 +17,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',
Expand Down
1 change: 1 addition & 0 deletions sql/updates/auth/2013_11_26_00_auth_account.sql
@@ -0,0 +1 @@
ALTER TABLE `account` ADD COLUMN `token_key` varchar(100) NOT NULL DEFAULT '' AFTER `s`;
97 changes: 97 additions & 0 deletions src/server/authserver/Authentication/TOTP.cpp
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2011-2013 Project SkyFire <http://www.projectskyfire.org/>
* Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2013 MaNGOS <http://getmangos.com/>
*
* 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 3 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"
#include <cstring>

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 == '\t' || 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;
}
}
31 changes: 31 additions & 0 deletions src/server/authserver/Authentication/TOTP.h
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2011-2013 Project SkyFire <http://www.projectskyfire.org/>
* Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2013 MaNGOS <http://getmangos.com/>
*
* 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 3 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
26 changes: 26 additions & 0 deletions src/server/authserver/Server/AuthSocket.cpp
Expand Up @@ -25,6 +25,7 @@
#include "RealmList.h"
#include "AuthSocket.h"
#include "AuthCodes.h"
#include "TOTP.h"
#include "SHA1.h"

#include <algorithm>
Expand Down Expand Up @@ -462,6 +463,12 @@ bool AuthSocket::_HandleLogonChallenge()
pkt.append(s.AsByteArray(), s.GetNumBytes()); // 32 bytes
pkt.append(unk3.AsByteArray(16), 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
Expand Down Expand Up @@ -622,6 +629,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) || (_expversion & POST_WOTLK_EXP_FLAG)) // 2.x, 3.x, 4.x
{
sAuthLogonProof_S proof;
Expand Down
1 change: 1 addition & 0 deletions src/server/authserver/Server/AuthSocket.h
Expand Up @@ -70,6 +70,7 @@ class AuthSocket: public RealmSocket::Session
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
Expand Down
5 changes: 5 additions & 0 deletions src/server/shared/Cryptography/HMACSHA1.cpp
Expand Up @@ -38,6 +38,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;
Expand Down
1 change: 1 addition & 0 deletions src/server/shared/Cryptography/HMACSHA1.h
Expand Up @@ -36,6 +36,7 @@ class HmacHash
~HmacHash();

void UpdateData(const std::string &str);
void UpdateData(const uint8* data, size_t len);
void Finalize();

uint8 *ComputeHash(BigNumber* bn);
Expand Down
Expand Up @@ -36,7 +36,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_GET_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_SET_VS, "UPDATE account SET v = ?, s = ? WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SET_LOGONPROOF, "UPDATE account SET sessionkey = ?, last_ip = ?, last_login = NOW(), locale = ?, failed_logins = 0, os = ? WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_GET_LOGONCHALLENGE, "SELECT a.sha_pass_hash, a.id, a.locked, 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_GET_LOGONCHALLENGE, "SELECT a.sha_pass_hash, a.id, a.locked, 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_SET_FAILEDLOGINS, "UPDATE account SET failed_logins = failed_logins + 1 WHERE username = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_GET_FAILEDLOGINS, "SELECT id, failed_logins FROM account WHERE username = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_GET_ACCIDBYNAME, "SELECT id FROM account WHERE username = ?", CONNECTION_SYNCH);
Expand Down

0 comments on commit 5161634

Please sign in to comment.