mirror of
https://github.com/znc/znc.git
synced 2026-07-03 16:31:49 +02:00
Merge pull request #1879 from DarthGandalf/argon
Update password hashes from SHA-256 to Argon2id
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@ clone_depth: 10
|
||||
install:
|
||||
- ps: Invoke-WebRequest https://cygwin.com/setup-x86_64.exe -OutFile c:\cygwin-setup.exe
|
||||
# libcrypt-devel is needed only on x86_64 and only for modperl... probably some dependency problem.
|
||||
- c:\cygwin-setup.exe --quiet-mode --no-shortcuts --no-startmenu --no-desktop --upgrade-also --only-site --site http://cygwin.mirror.constant.com/ --root c:\cygwin-root --local-package-dir c:\cygwin-setup-cache --packages gcc-g++,make,pkg-config,wget,libssl-devel,libicu-devel,zlib-devel,libcrypt-devel,perl,python3-devel,swig,libsasl2-devel,libQt5Core-devel,cmake,libboost-devel,gettext-devel
|
||||
- c:\cygwin-setup.exe --quiet-mode --no-shortcuts --no-startmenu --no-desktop --upgrade-also --only-site --site http://cygwin.mirror.constant.com/ --root c:\cygwin-root --local-package-dir c:\cygwin-setup-cache --packages gcc-g++,make,pkg-config,wget,libssl-devel,libicu-devel,zlib-devel,libcrypt-devel,perl,python3-devel,swig,libsasl2-devel,libQt5Core-devel,cmake,libboost-devel,gettext-devel,libargon2-devel
|
||||
- c:\cygwin-root\bin\sh -lc "echo Hi"
|
||||
- c:\cygwin-root\bin\sh -lc "uname -a"
|
||||
- c:\cygwin-root\bin\sh -lc "cat /proc/cpuinfo"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y tcl-dev libsasl2-dev libicu-dev swig qtbase5-dev libboost-locale-dev libperl-dev cpanminus gettext clang llvm lcov
|
||||
sudo apt-get install -y tcl-dev libsasl2-dev libicu-dev swig qtbase5-dev libboost-locale-dev libperl-dev libargon2-dev cpanminus gettext clang llvm lcov
|
||||
sudo apt-get upgrade -y
|
||||
|
||||
|
||||
+8
-1
@@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(ZNC VERSION 1.9.0 LANGUAGES CXX)
|
||||
set(ZNC_VERSION 1.9.x)
|
||||
set(append_git_version true)
|
||||
@@ -135,6 +135,12 @@ if(WANT_CYRUS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
tristate_option(ARGON "Store password hashes using Argon2id instead of SHA-256")
|
||||
if(WANT_ARGON)
|
||||
pkg_check_modules(ARGON ${TRISTATE_ARGON_REQUIRED} IMPORTED_TARGET libargon2)
|
||||
endif()
|
||||
set(ZNC_HAVE_ARGON "${ARGON_FOUND}")
|
||||
|
||||
tristate_option(ICU "Support character encodings")
|
||||
if(WANT_ICU)
|
||||
pkg_check_modules(ICU ${TRISTATE_ICU_REQUIRED} icu-uc)
|
||||
@@ -447,6 +453,7 @@ summary_line("Cyrus " "${CYRUS_FOUND}")
|
||||
summary_line("Charset " "${ICU_FOUND}")
|
||||
summary_line("Zlib " "${ZLIB_FOUND}")
|
||||
summary_line("i18n " "${HAVE_I18N}")
|
||||
summary_line("Argon2 " "${ZNC_HAVE_ARGON}")
|
||||
|
||||
include(render_framed_multiline)
|
||||
render_framed_multiline("${summary_lines}")
|
||||
|
||||
@@ -15,6 +15,7 @@ RUN set -x \
|
||||
&& adduser -S znc \
|
||||
&& addgroup -S znc
|
||||
RUN apk add --no-cache \
|
||||
argon2-libs \
|
||||
boost \
|
||||
build-base \
|
||||
ca-certificates \
|
||||
@@ -30,6 +31,7 @@ RUN apk add --no-cache \
|
||||
tini \
|
||||
tzdata
|
||||
RUN apk add --no-cache --virtual build-dependencies \
|
||||
argon2-dev \
|
||||
boost-dev \
|
||||
cyrus-sasl-dev \
|
||||
perl-dev \
|
||||
|
||||
@@ -83,6 +83,7 @@ tristate('cyrus')
|
||||
tristate('charset', 'ICU')
|
||||
tristate('tcl')
|
||||
tristate('i18n')
|
||||
tristate('argon')
|
||||
|
||||
class HandlePython(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
|
||||
+10
-7
@@ -45,24 +45,27 @@ class CUser : private CCoreTranslationMixin {
|
||||
|
||||
bool ParseConfig(CConfig* Config, CString& sError);
|
||||
|
||||
// TODO refactor this
|
||||
enum eHashType {
|
||||
HASH_NONE,
|
||||
HASH_MD5,
|
||||
HASH_SHA256,
|
||||
HASH_ARGON2ID,
|
||||
|
||||
HASH_DEFAULT = HASH_SHA256
|
||||
// This should be kept in sync with CUtils::SaltedHash
|
||||
#if ZNC_HAVE_ARGON
|
||||
HASH_DEFAULT = HASH_ARGON2ID,
|
||||
#else
|
||||
HASH_DEFAULT = HASH_SHA256,
|
||||
#endif
|
||||
};
|
||||
|
||||
// If you change the default hash here and in HASH_DEFAULT,
|
||||
// don't forget CUtils::sDefaultHash!
|
||||
// TODO refactor this
|
||||
static CString SaltedHash(const CString& sPass, const CString& sSalt) {
|
||||
return CUtils::SaltedSHA256Hash(sPass, sSalt);
|
||||
return CUtils::SaltedHash(sPass, sSalt);
|
||||
}
|
||||
|
||||
CConfig ToConfig() const;
|
||||
bool CheckPass(const CString& sPass) const;
|
||||
/** Checks password, may upgrade the hash method. */
|
||||
bool CheckPass(const CString& sPass);
|
||||
bool AddAllowedHost(const CString& sHostMask);
|
||||
bool RemAllowedHost(const CString& sHostMask);
|
||||
void ClearAllowedHosts();
|
||||
|
||||
+6
-5
@@ -51,15 +51,16 @@ class CUtils {
|
||||
static void PrintAction(const CString& sMessage);
|
||||
static void PrintStatus(bool bSuccess, const CString& sMessage = "");
|
||||
|
||||
#ifndef SWIGPERL
|
||||
// TODO refactor this
|
||||
static const CString sDefaultHash;
|
||||
#endif
|
||||
/** Asks password from stdin, with confirmation.
|
||||
*
|
||||
* @returns Piece of znc.conf with <Pass> block
|
||||
* */
|
||||
static CString AskSaltedHashPassForConfig();
|
||||
|
||||
static CString GetSaltedHashPass(CString& sSalt);
|
||||
static CString GetSalt();
|
||||
static CString SaltedMD5Hash(const CString& sPass, const CString& sSalt);
|
||||
static CString SaltedSHA256Hash(const CString& sPass, const CString& sSalt);
|
||||
static CString SaltedHash(const CString& sPass, const CString& sSalt);
|
||||
static CString GetPass(const CString& sPrompt);
|
||||
static bool GetInput(const CString& sPrompt, CString& sRet,
|
||||
const CString& sDefault = "",
|
||||
|
||||
@@ -57,9 +57,16 @@ extern const char* ZNC_VERSION_EXTRA;
|
||||
#define ZNC_VERSION_TEXT_I18N "no"
|
||||
#endif
|
||||
|
||||
// This is only here because HASH_DEFAULT has different value
|
||||
#ifdef ZNC_HAVE_ARGON
|
||||
#define ZNC_VERSION_TEXT_ARGON "yes"
|
||||
#else
|
||||
#define ZNC_VERSION_TEXT_ARGON "no"
|
||||
#endif
|
||||
|
||||
#define ZNC_COMPILE_OPTIONS_STRING \
|
||||
"IPv6: " ZNC_VERSION_TEXT_IPV6 ", SSL: " ZNC_VERSION_TEXT_SSL \
|
||||
", DNS: " ZNC_VERSION_TEXT_DNS ", charset: " ZNC_VERSION_TEXT_ICU \
|
||||
", i18n: " ZNC_VERSION_TEXT_I18N
|
||||
", i18n: " ZNC_VERSION_TEXT_I18N ", Argon2: " ZNC_VERSION_TEXT_ARGON
|
||||
|
||||
#endif // !ZNC_VERSION_H
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#cmakedefine HAVE_IPV6 1
|
||||
#cmakedefine HAVE_ZLIB 1
|
||||
#cmakedefine HAVE_I18N 1
|
||||
#cmakedefine ZNC_HAVE_ARGON 1
|
||||
#cmakedefine CSOCK_USE_POLL 1
|
||||
|
||||
#cmakedefine HAVE_GETOPT_LONG 1
|
||||
|
||||
@@ -82,6 +82,9 @@ if(Boost_FOUND)
|
||||
target_link_libraries(znclib PRIVATE ${Boost_LIBRARIES})
|
||||
list(APPEND znc_include_dirs ${Boost_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(ZNC_HAVE_ARGON)
|
||||
target_link_libraries(znclib PRIVATE PkgConfig::ARGON)
|
||||
endif()
|
||||
target_link_libraries(znclib PRIVATE cctz::cctz)
|
||||
target_include_directories(znc PUBLIC ${znc_include_dirs})
|
||||
target_include_directories(znclib PUBLIC ${znc_include_dirs})
|
||||
|
||||
+51
-6
@@ -25,6 +25,10 @@
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef ZNC_HAVE_ARGON
|
||||
#include <argon2.h>
|
||||
#endif
|
||||
|
||||
using std::vector;
|
||||
using std::set;
|
||||
|
||||
@@ -346,7 +350,12 @@ bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
|
||||
method = CUser::HASH_MD5;
|
||||
else if (sMethod.Equals("sha256"))
|
||||
method = CUser::HASH_SHA256;
|
||||
else {
|
||||
else if (sMethod.Equals("Argon2id")) {
|
||||
method = CUser::HASH_ARGON2ID;
|
||||
#ifndef ZNC_HAVE_ARGON
|
||||
CUtils::PrintError("ZNC is built without Argon2 support, " + GetUsername() + " won't be able to authenticate");
|
||||
#endif
|
||||
} else {
|
||||
sError = "Invalid hash method";
|
||||
CUtils::PrintError(sError);
|
||||
return false;
|
||||
@@ -958,6 +967,9 @@ CConfig CUser::ToConfig() const {
|
||||
case HASH_SHA256:
|
||||
sHash = "SHA256";
|
||||
break;
|
||||
case HASH_ARGON2ID:
|
||||
sHash = "Argon2id";
|
||||
break;
|
||||
}
|
||||
passConfig.AddKeyValuePair("Salt", m_sPassSalt);
|
||||
passConfig.AddKeyValuePair("Method", sHash);
|
||||
@@ -1042,20 +1054,43 @@ CConfig CUser::ToConfig() const {
|
||||
return config;
|
||||
}
|
||||
|
||||
bool CUser::CheckPass(const CString& sPass) const {
|
||||
bool CUser::CheckPass(const CString& sPass) {
|
||||
if(AuthOnlyViaModule() || CZNC::Get().GetAuthOnlyViaModule()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bResult = false;
|
||||
bool bUpgrade = false;
|
||||
switch (m_eHashType) {
|
||||
case HASH_MD5:
|
||||
return m_sPass.Equals(CUtils::SaltedMD5Hash(sPass, m_sPassSalt));
|
||||
bResult = m_sPass.Equals(CUtils::SaltedMD5Hash(sPass, m_sPassSalt));
|
||||
bUpgrade = true;
|
||||
break;
|
||||
case HASH_SHA256:
|
||||
return m_sPass.Equals(CUtils::SaltedSHA256Hash(sPass, m_sPassSalt));
|
||||
bResult = m_sPass.Equals(CUtils::SaltedSHA256Hash(sPass, m_sPassSalt));
|
||||
#if ZNC_HAVE_ARGON
|
||||
bUpgrade = true;
|
||||
#endif
|
||||
break;
|
||||
case HASH_ARGON2ID:
|
||||
#if ZNC_HAVE_ARGON
|
||||
return argon2id_verify(m_sPass.c_str(), sPass.data(), sPass.length()) == ARGON2_OK;
|
||||
#else
|
||||
CUtils::PrintError("ZNC is built without Argon2 support, " + GetUsername() + " cannot authenticate");
|
||||
return false;
|
||||
#endif
|
||||
case HASH_NONE:
|
||||
default:
|
||||
// Don't upgrade hash, since the only valid use case for plain are
|
||||
// manual tests, where it's simpler this way
|
||||
return (sPass == m_sPass);
|
||||
}
|
||||
|
||||
if (bResult && bUpgrade) {
|
||||
CString sSalt = CUtils::GetSalt();
|
||||
CString sHash = CUser::SaltedHash(sPass, sSalt);
|
||||
SetPass(sHash, CUser::HASH_DEFAULT, sSalt);
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
|
||||
/*CClient* CUser::GetClient() {
|
||||
@@ -1271,7 +1306,17 @@ void CUser::SetDCCBindHost(const CString& s) { m_sDCCBindHost = s; }
|
||||
void CUser::SetPass(const CString& s, eHashType eHash, const CString& sSalt) {
|
||||
m_sPass = s;
|
||||
m_eHashType = eHash;
|
||||
m_sPassSalt = sSalt;
|
||||
switch (eHash) {
|
||||
case HASH_NONE:
|
||||
case HASH_ARGON2ID:
|
||||
// Salt is embedded in the encoded "hash" in argon
|
||||
m_sPassSalt = "";
|
||||
break;
|
||||
case HASH_MD5:
|
||||
case HASH_SHA256:
|
||||
m_sPassSalt = sSalt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
void CUser::SetMultiClients(bool b) { m_bMultiClients = b; }
|
||||
void CUser::SetDenyLoadMod(bool b) { m_bDenyLoadMod = b; }
|
||||
|
||||
+42
-7
@@ -52,6 +52,10 @@
|
||||
#include <unicode/errorcode.h>
|
||||
#endif
|
||||
|
||||
#ifdef ZNC_HAVE_ARGON
|
||||
#include <argon2.h>
|
||||
#endif
|
||||
|
||||
// Required with GCC 4.3+ if openssl is disabled
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
@@ -176,12 +180,24 @@ unsigned long CUtils::GetLongIP(const CString& sIP) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// If you change this here and in GetSaltedHashPass(),
|
||||
// don't forget CUser::HASH_DEFAULT!
|
||||
// TODO refactor this
|
||||
const CString CUtils::sDefaultHash = "sha256";
|
||||
CString CUtils::GetSaltedHashPass(CString& sSalt) {
|
||||
sSalt = GetSalt();
|
||||
#ifdef ZNC_HAVE_ARGON
|
||||
static CString SaltedArgonHash(const CString& sPass, const CString& sSalt) {
|
||||
#define ZNC_ARGON_PARAMS /* iterations */ 6, /* memory */ 6144, /* parallelism */ 1
|
||||
constexpr int iHashLen = 32;
|
||||
CString sOut;
|
||||
sOut.resize(argon2_encodedlen(ZNC_ARGON_PARAMS, sSalt.length(), iHashLen, Argon2_id) + 1);
|
||||
int err = argon2id_hash_encoded(ZNC_ARGON_PARAMS, sPass.data(), sPass.length(), sSalt.data(), sSalt.length(), iHashLen, &sOut[0], sOut.length());
|
||||
if (err) {
|
||||
CUtils::PrintError(argon2_error_message(err));
|
||||
sOut.clear();
|
||||
}
|
||||
return sOut;
|
||||
}
|
||||
#undef ZNC_ARGON_PARAMS
|
||||
#endif
|
||||
|
||||
CString CUtils::AskSaltedHashPassForConfig() {
|
||||
CString sSalt = GetSalt();
|
||||
|
||||
while (true) {
|
||||
CString pass1;
|
||||
@@ -195,7 +211,18 @@ CString CUtils::GetSaltedHashPass(CString& sSalt) {
|
||||
CUtils::PrintError("The supplied passwords did not match");
|
||||
} else {
|
||||
// Construct the salted pass
|
||||
return SaltedSHA256Hash(pass1, sSalt);
|
||||
VCString vsLines;
|
||||
vsLines.push_back("<Pass password>");
|
||||
#if ZNC_HAVE_ARGON
|
||||
vsLines.push_back("\tMethod = Argon2id");
|
||||
vsLines.push_back("\tHash = " + SaltedArgonHash(pass1, sSalt));
|
||||
#else
|
||||
vsLines.push_back("\tMethod = SHA256");
|
||||
vsLines.push_back("\tHash = " + SaltedSHA256Hash(pass1, sSalt));
|
||||
vsLines.push_back("\tSalt = " + sSalt);
|
||||
#endif
|
||||
vsLines.push_back("</Pass>");
|
||||
return CString("\n").Join(vsLines.begin(), vsLines.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,6 +237,14 @@ CString CUtils::SaltedSHA256Hash(const CString& sPass, const CString& sSalt) {
|
||||
return CString(sPass + sSalt).SHA256();
|
||||
}
|
||||
|
||||
CString CUtils::SaltedHash(const CString& sPass, const CString& sSalt) {
|
||||
#ifdef ZNC_HAVE_ARGON
|
||||
return SaltedArgonHash(sPass, sSalt);
|
||||
#else
|
||||
return SaltedSHA256Hash(sPass, sSalt);
|
||||
#endif
|
||||
}
|
||||
|
||||
CString CUtils::GetPass(const CString& sPrompt) {
|
||||
#ifdef HAVE_TCSETATTR
|
||||
// Disable echo
|
||||
|
||||
+2
-7
@@ -383,20 +383,15 @@ int main(int argc, char** argv) {
|
||||
#endif /* HAVE_LIBSSL */
|
||||
|
||||
if (bMakePass) {
|
||||
CString sSalt;
|
||||
CUtils::PrintMessage("Type your new password.");
|
||||
CString sHash = CUtils::GetSaltedHashPass(sSalt);
|
||||
CString sPass = CUtils::AskSaltedHashPassForConfig();
|
||||
CUtils::PrintMessage("Kill ZNC process, if it's running.");
|
||||
CUtils::PrintMessage(
|
||||
"Then replace password in the <User> section of your config with "
|
||||
"this:");
|
||||
// Not PrintMessage(), to remove [**] from the beginning, to ease
|
||||
// copypasting
|
||||
std::cout << "<Pass password>" << std::endl;
|
||||
std::cout << "\tMethod = " << CUtils::sDefaultHash << std::endl;
|
||||
std::cout << "\tHash = " << sHash << std::endl;
|
||||
std::cout << "\tSalt = " << sSalt << std::endl;
|
||||
std::cout << "</Pass>" << std::endl;
|
||||
std::cout << sPass << std::endl;
|
||||
CUtils::PrintMessage(
|
||||
"After that start ZNC again, and you should be able to login with "
|
||||
"the new password.");
|
||||
|
||||
+2
-4
@@ -734,10 +734,8 @@ bool CZNC::WriteNewConfig(const CString& sConfigFile) {
|
||||
} while (!CUser::IsValidUsername(sUser));
|
||||
|
||||
vsLines.push_back("<User " + sUser + ">");
|
||||
CString sSalt;
|
||||
sAnswer = CUtils::GetSaltedHashPass(sSalt);
|
||||
vsLines.push_back("\tPass = " + CUtils::sDefaultHash + "#" + sAnswer +
|
||||
"#" + sSalt + "#");
|
||||
sAnswer = CUtils::AskSaltedHashPassForConfig();
|
||||
vsLines.push_back(sAnswer);
|
||||
|
||||
vsLines.push_back("\tAdmin = true");
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "znctest.h"
|
||||
|
||||
using testing::HasSubstr;
|
||||
using testing::ContainsRegex;
|
||||
|
||||
namespace znc_inttest {
|
||||
namespace {
|
||||
@@ -430,5 +431,49 @@ TEST_F(ZNCTest, DenyOptions) {
|
||||
client2.ReadUntil("Access denied!");
|
||||
}
|
||||
|
||||
TEST_F(ZNCTest, HashUpgrade) {
|
||||
QFile conf(m_dir.path() + "/configs/znc.conf");
|
||||
ASSERT_TRUE(conf.open(QIODevice::Append | QIODevice::Text));
|
||||
QTextStream out(&conf);
|
||||
out << R"(
|
||||
<User foo>
|
||||
<Pass pass>
|
||||
Method = MD5
|
||||
Salt = abc
|
||||
Hash = defdf93cef7fa7a8ee88e65d0e277b99
|
||||
</Pass>
|
||||
</User>
|
||||
)";
|
||||
out.flush();
|
||||
conf.close();
|
||||
auto znc = Run();
|
||||
auto ircd = ConnectIRCd();
|
||||
|
||||
auto client = ConnectClient();
|
||||
client.Write("PASS :hunter2");
|
||||
client.Write("NICK nick");
|
||||
client.Write("USER foo x x :x");
|
||||
client.ReadUntil("Welcome");
|
||||
client.Close();
|
||||
|
||||
client = LoginClient();
|
||||
client.Write("znc saveconfig");
|
||||
client.ReadUntil("Wrote config");
|
||||
|
||||
ASSERT_TRUE(conf.open(QIODevice::ReadOnly | QIODevice::Text));
|
||||
QTextStream in(&conf);
|
||||
QString config = in.readAll();
|
||||
// It was upgraded to either Argon2 or SHA256
|
||||
EXPECT_THAT(config.toStdString(), Not(ContainsRegex("Method.*MD5")));
|
||||
|
||||
// Check that still can login after the upgrade
|
||||
client = ConnectClient();
|
||||
client.Write("PASS :hunter2");
|
||||
client.Write("NICK nick");
|
||||
client.Write("USER foo x x :x");
|
||||
client.ReadUntil("Welcome");
|
||||
client.Close();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace znc_inttest
|
||||
|
||||
Reference in New Issue
Block a user