Add optional two-factor authentication primitives

This commit is contained in:
Mario Fetka
2026-07-17 01:55:37 +02:00
parent 21599c3893
commit 252ddd4a45
8 changed files with 376 additions and 1 deletions
+1 -1
View File
@@ -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")
+36
View File
@@ -0,0 +1,36 @@
# /****************************************************************************
# * <Novell-copyright>
# * 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.
# * </Novell-copyright>
# ****************************************************************************/
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)
+44
View File
@@ -0,0 +1,44 @@
# /****************************************************************************
# * <Novell-copyright>
# * 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.
# * </Novell-copyright>
# ****************************************************************************/
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)
+4
View File
@@ -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)
+38
View File
@@ -0,0 +1,38 @@
/****************************************************************************
* <Novell-copyright>
* 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.
* </Novell-copyright>
****************************************************************************/
#ifndef BONGO_AUTH_SECURITY_H
#define BONGO_AUTH_SECURITY_H
#include <stddef.h>
#include <time.h>
#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
+16
View File
@@ -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()
+173
View File
@@ -0,0 +1,173 @@
/****************************************************************************
* <Novell-copyright>
* 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.
* </Novell-copyright>
****************************************************************************/
#include <argon2.h>
#include <gcrypt.h>
#include <liboath/oath.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <bongoauthsecurity.h>
#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;
}
@@ -0,0 +1,64 @@
/****************************************************************************
* <Novell-copyright>
* 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.
* </Novell-copyright>
****************************************************************************/
#include <liboath/oath.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bongoauthsecurity.h>
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;
}