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; +}