diff --git a/CMakeLists.txt b/CMakeLists.txt
index d1ebd0f..32fe16a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,7 +46,7 @@ include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/include/)
# now build our various libraries, agents, binaries.
# DO NOT ALTER (SO)VERSION ON LIBRARIES WITHOUT ASKING!
-foreach (LIBRARY xpl streamio msgapi connio util json cal mailauth sieve sasl collectoraccounts)
+foreach (LIBRARY xpl streamio msgapi connio util json cal mailauth sieve sasl collectoraccounts authsecurity)
add_subdirectory (src/libs/${LIBRARY})
set(LIBRARY_TARGET bongo${LIBRARY})
if(LIBRARY STREQUAL "sieve")
diff --git a/cmake/FindArgon2.cmake b/cmake/FindArgon2.cmake
new file mode 100644
index 0000000..a01dfc3
--- /dev/null
+++ b/cmake/FindArgon2.cmake
@@ -0,0 +1,36 @@
+# /****************************************************************************
+# *
+# * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
+# *
+# * This program is free software; you can redistribute it and/or
+# * modify it under the terms of version 2 of the GNU General Public License
+# * as published by the Free Software Foundation.
+# *
+# * 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, contact Novell, Inc.
+# *
+# * To contact Novell about this file by physical or electronic mail, you
+# * may find current contact information at www.novell.com.
+# *
+# ****************************************************************************/
+
+find_path(Argon2_INCLUDE_DIR NAMES argon2.h)
+find_library(Argon2_LIBRARY NAMES argon2)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Argon2
+ REQUIRED_VARS Argon2_LIBRARY Argon2_INCLUDE_DIR)
+
+if(Argon2_FOUND AND NOT TARGET Argon2::Argon2)
+ add_library(Argon2::Argon2 UNKNOWN IMPORTED)
+ set_target_properties(Argon2::Argon2 PROPERTIES
+ IMPORTED_LOCATION "${Argon2_LIBRARY}"
+ INTERFACE_INCLUDE_DIRECTORIES "${Argon2_INCLUDE_DIR}")
+endif()
+
+mark_as_advanced(Argon2_INCLUDE_DIR Argon2_LIBRARY)
diff --git a/cmake/FindOATH.cmake b/cmake/FindOATH.cmake
new file mode 100644
index 0000000..1174c1f
--- /dev/null
+++ b/cmake/FindOATH.cmake
@@ -0,0 +1,44 @@
+# /****************************************************************************
+# *
+# * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
+# *
+# * This program is free software; you can redistribute it and/or
+# * modify it under the terms of version 2 of the GNU General Public License
+# * as published by the Free Software Foundation.
+# *
+# * 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, contact Novell, Inc.
+# *
+# * To contact Novell about this file by physical or electronic mail, you
+# * may find current contact information at www.novell.com.
+# *
+# ****************************************************************************/
+
+find_path(OATH_INCLUDE_DIR NAMES liboath/oath.h)
+find_library(OATH_LIBRARY NAMES oath)
+
+if(OATH_INCLUDE_DIR)
+ file(STRINGS "${OATH_INCLUDE_DIR}/liboath/oath.h" _OATH_VERSION_LINE
+ REGEX "^# *define OATH_VERSION +\"[0-9]+\\.[0-9]+\\.[0-9]+\"")
+ string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" OATH_VERSION
+ "${_OATH_VERSION_LINE}")
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(OATH
+ REQUIRED_VARS OATH_LIBRARY OATH_INCLUDE_DIR
+ VERSION_VAR OATH_VERSION)
+
+if(OATH_FOUND AND NOT TARGET OATH::OATH)
+ add_library(OATH::OATH UNKNOWN IMPORTED)
+ set_target_properties(OATH::OATH PROPERTIES
+ IMPORTED_LOCATION "${OATH_LIBRARY}"
+ INTERFACE_INCLUDE_DIRECTORIES "${OATH_INCLUDE_DIR}")
+endif()
+
+mark_as_advanced(OATH_INCLUDE_DIR OATH_LIBRARY)
diff --git a/cmake/Requirements.cmake b/cmake/Requirements.cmake
index 6bcfc82..613f7b4 100644
--- a/cmake/Requirements.cmake
+++ b/cmake/Requirements.cmake
@@ -58,6 +58,10 @@ find_package(Mailutils REQUIRED COMPONENTS Sieve)
# Shared SASL implementation for SMTP, IMAP, POP3 and ManageSieve.
find_package(CyrusSASL 2.1.28 REQUIRED)
+# TOTP, recovery codes and service-scoped app passwords.
+find_package(OATH 2.6 REQUIRED)
+find_package(Argon2 REQUIRED)
+
# Native mail authentication. Bongo links to the protocol libraries directly;
# it does not require the corresponding milter daemons at runtime.
find_package(SPF2 REQUIRED)
diff --git a/include/bongoauthsecurity.h b/include/bongoauthsecurity.h
new file mode 100644
index 0000000..a8b3433
--- /dev/null
+++ b/include/bongoauthsecurity.h
@@ -0,0 +1,38 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * 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, contact Novell, Inc.
+ *
+ ****************************************************************************/
+
+#ifndef BONGO_AUTH_SECURITY_H
+#define BONGO_AUTH_SECURITY_H
+
+#include
+#include
+
+#define BONGO_AUTH_TOTP_SECRET_SIZE 64
+#define BONGO_AUTH_CREDENTIAL_SIZE 48
+#define BONGO_AUTH_HASH_SIZE 128
+
+int BongoAuthSecurityInit(void);
+void BongoAuthSecurityDone(void);
+int BongoAuthGenerateTotpSecret(char *result, size_t result_size);
+int BongoAuthVerifyTotp(const char *secret, const char *otp, time_t now);
+int BongoAuthGenerateCredential(char *result, size_t result_size);
+int BongoAuthHashCredential(const char *credential, char *result,
+ size_t result_size);
+int BongoAuthVerifyCredential(const char *encoded, const char *credential);
+
+#endif
diff --git a/src/libs/authsecurity/CMakeLists.txt b/src/libs/authsecurity/CMakeLists.txt
new file mode 100644
index 0000000..9f2678e
--- /dev/null
+++ b/src/libs/authsecurity/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_library(bongoauthsecurity SHARED
+ authsecurity.c)
+
+target_link_libraries(bongoauthsecurity
+ Argon2::Argon2
+ LibGcrypt::LibGcrypt
+ OATH::OATH)
+
+install(TARGETS bongoauthsecurity DESTINATION ${LIB_INSTALL_DIR})
+StrictCompile()
+
+if(BUILD_TESTING)
+ add_executable(auth-security-test tests/authsecurity-test.c)
+ target_link_libraries(auth-security-test PRIVATE bongoauthsecurity OATH::OATH)
+ add_test(NAME auth-security COMMAND auth-security-test)
+endif()
diff --git a/src/libs/authsecurity/authsecurity.c b/src/libs/authsecurity/authsecurity.c
new file mode 100644
index 0000000..ef31a20
--- /dev/null
+++ b/src/libs/authsecurity/authsecurity.c
@@ -0,0 +1,173 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * 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, contact Novell, Inc.
+ *
+ ****************************************************************************/
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+#define TOTP_SECRET_BYTES 20U
+#define CREDENTIAL_BYTES 20U
+#define ARGON_TIME_COST 3U
+#define ARGON_MEMORY_COST 32768U
+#define ARGON_PARALLELISM 1U
+#define ARGON_SALT_BYTES 16U
+#define ARGON_HASH_BYTES 32U
+
+static void
+SecureClear(void *buffer, size_t length)
+{
+ volatile unsigned char *cursor = buffer;
+
+ while (length-- > 0)
+ *cursor++ = 0;
+}
+
+int
+BongoAuthSecurityInit(void)
+{
+ if (!gcry_check_version(NULL))
+ return -1;
+ return oath_init() == OATH_OK ? 0 : -1;
+}
+
+void
+BongoAuthSecurityDone(void)
+{
+ oath_done();
+}
+
+int
+BongoAuthGenerateTotpSecret(char *result, size_t result_size)
+{
+ unsigned char random[TOTP_SECRET_BYTES];
+ char *encoded = NULL;
+ size_t encoded_size = 0;
+ int status = -1;
+
+ if (!result || result_size == 0)
+ return -1;
+ gcry_randomize(random, sizeof(random), GCRY_STRONG_RANDOM);
+ if (oath_base32_encode((const char *)random, sizeof(random), &encoded,
+ &encoded_size) == OATH_OK && encoded &&
+ encoded_size < result_size) {
+ memcpy(result, encoded, encoded_size);
+ result[encoded_size] = '\0';
+ status = 0;
+ }
+ if (encoded) {
+ SecureClear(encoded, encoded_size);
+ free(encoded);
+ }
+ SecureClear(random, sizeof(random));
+ return status;
+}
+
+int
+BongoAuthVerifyTotp(const char *secret, const char *otp, time_t now)
+{
+ char *decoded = NULL;
+ size_t decoded_size = 0;
+ int status;
+ size_t i;
+
+ if (!secret || !otp || strlen(otp) != 6)
+ return 0;
+ for (i = 0; otp[i]; i++)
+ if (!isdigit((unsigned char)otp[i]))
+ return 0;
+ if (oath_base32_decode(secret, strlen(secret), &decoded,
+ &decoded_size) != OATH_OK || !decoded)
+ return 0;
+ status = oath_totp_validate(decoded, decoded_size, now,
+ OATH_TOTP_DEFAULT_TIME_STEP_SIZE,
+ OATH_TOTP_DEFAULT_START_TIME, 1, otp);
+ SecureClear(decoded, decoded_size);
+ free(decoded);
+ return status >= 0;
+}
+
+int
+BongoAuthGenerateCredential(char *result, size_t result_size)
+{
+ unsigned char random[CREDENTIAL_BYTES];
+ char *encoded = NULL;
+ size_t encoded_size = 0;
+ size_t i;
+ size_t output = 0;
+ int status = -1;
+
+ if (!result || result_size < BONGO_AUTH_CREDENTIAL_SIZE)
+ return -1;
+ gcry_randomize(random, sizeof(random), GCRY_STRONG_RANDOM);
+ if (oath_base32_encode((const char *)random, sizeof(random), &encoded,
+ &encoded_size) != OATH_OK || !encoded)
+ goto cleanup;
+ for (i = 0; i < encoded_size && encoded[i] != '='; i++) {
+ if (i > 0 && i % 4 == 0)
+ result[output++] = '-';
+ result[output++] = (char)tolower((unsigned char)encoded[i]);
+ }
+ result[output] = '\0';
+ status = 0;
+
+cleanup:
+ if (encoded) {
+ SecureClear(encoded, encoded_size);
+ free(encoded);
+ }
+ SecureClear(random, sizeof(random));
+ return status;
+}
+
+int
+BongoAuthHashCredential(const char *credential, char *result,
+ size_t result_size)
+{
+ unsigned char salt[ARGON_SALT_BYTES];
+ size_t required;
+ int status;
+
+ if (!credential || !credential[0] || !result)
+ return -1;
+ required = argon2_encodedlen(ARGON_TIME_COST, ARGON_MEMORY_COST,
+ ARGON_PARALLELISM, ARGON_SALT_BYTES,
+ ARGON_HASH_BYTES, Argon2_id);
+ if (result_size < required)
+ return -1;
+ gcry_randomize(salt, sizeof(salt), GCRY_STRONG_RANDOM);
+ status = argon2id_hash_encoded(ARGON_TIME_COST, ARGON_MEMORY_COST,
+ ARGON_PARALLELISM, credential,
+ strlen(credential), salt, sizeof(salt),
+ ARGON_HASH_BYTES, result, result_size);
+ SecureClear(salt, sizeof(salt));
+ return status == ARGON2_OK ? 0 : -1;
+}
+
+int
+BongoAuthVerifyCredential(const char *encoded, const char *credential)
+{
+ if (!encoded || !credential)
+ return 0;
+ return argon2id_verify(encoded, credential, strlen(credential)) == ARGON2_OK;
+}
diff --git a/src/libs/authsecurity/tests/authsecurity-test.c b/src/libs/authsecurity/tests/authsecurity-test.c
new file mode 100644
index 0000000..cd90df1
--- /dev/null
+++ b/src/libs/authsecurity/tests/authsecurity-test.c
@@ -0,0 +1,64 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * 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, contact Novell, Inc.
+ *
+ * To contact Novell about this file by physical or electronic mail, you
+ * may find current contact information at www.novell.com.
+ *
+ ****************************************************************************/
+
+#include
+#include
+#include
+#include
+
+#include
+
+int
+main(void)
+{
+ char secret[BONGO_AUTH_TOTP_SECRET_SIZE];
+ char credential[BONGO_AUTH_CREDENTIAL_SIZE];
+ char second[BONGO_AUTH_CREDENTIAL_SIZE];
+ char hash[BONGO_AUTH_HASH_SIZE];
+ char otp[7];
+ char *decoded = NULL;
+ size_t decoded_size = 0;
+ time_t now = 1234567890;
+ int result = 0;
+
+ if (BongoAuthSecurityInit() != 0)
+ return 1;
+ if (BongoAuthGenerateTotpSecret(secret, sizeof(secret)) != 0 ||
+ oath_base32_decode(secret, strlen(secret), &decoded,
+ &decoded_size) != OATH_OK)
+ result = 2;
+ else if (oath_totp_generate(decoded, decoded_size, now, 30, 0, 6, otp) != OATH_OK ||
+ !BongoAuthVerifyTotp(secret, otp, now) ||
+ BongoAuthVerifyTotp(secret, "00000x", now))
+ result = 3;
+ free(decoded);
+ if (!result && (BongoAuthGenerateCredential(credential, sizeof(credential)) != 0 ||
+ BongoAuthGenerateCredential(second, sizeof(second)) != 0 ||
+ strcmp(credential, second) == 0 || strlen(credential) != 39))
+ result = 4;
+ if (!result && (BongoAuthHashCredential(credential, hash, sizeof(hash)) != 0 ||
+ strncmp(hash, "$argon2id$", 10) != 0 ||
+ !BongoAuthVerifyCredential(hash, credential) ||
+ BongoAuthVerifyCredential(hash, second)))
+ result = 5;
+ BongoAuthSecurityDone();
+ return result;
+}