diff --git a/client/cmd_opts.h b/client/cmd_opts.h index 3cb75453..3b0ef30c 100644 --- a/client/cmd_opts.h +++ b/client/cmd_opts.h @@ -66,6 +66,7 @@ enum { FD_SET_STDIN, FD_SET_ALT, FAULT_INJECTION_TAG, + USE_TOTP, /* Put GPG-related items below the following line */ GPG_ENCRYPTION = 0x200, @@ -158,6 +159,7 @@ static struct option cmd_opts[] = {"verbose", 0, NULL, 'v'}, {"version", 0, NULL, 'V'}, {"wget-cmd", 1, NULL, 'w'}, + {"totp", 0, NULL, USE_TOTP}, {0, 0, 0, 0} }; diff --git a/client/config_init.c b/client/config_init.c index dab1cbac..ce890f80 100644 --- a/client/config_init.c +++ b/client/config_init.c @@ -132,6 +132,7 @@ enum FWKNOP_CLI_ARG_RESOLVE_IP_HTTPS, FWKNOP_CLI_ARG_RESOLVE_HTTP_ONLY, FWKNOP_CLI_ARG_WGET_CMD, + FWKNOP_CLI_ARG_USE_TOTP, FWKNOP_CLI_ARG_NO_SAVE_ARGS, FWKNOP_CLI_LAST_ARG } fwknop_cli_arg_t; @@ -181,7 +182,8 @@ static fko_var_t fko_var_array[FWKNOP_CLI_LAST_ARG] = { "RESOLVE_IP_HTTPS", FWKNOP_CLI_ARG_RESOLVE_IP_HTTPS }, { "RESOLVE_HTTP_ONLY", FWKNOP_CLI_ARG_RESOLVE_HTTP_ONLY }, { "WGET_CMD", FWKNOP_CLI_ARG_WGET_CMD }, - { "NO_SAVE_ARGS", FWKNOP_CLI_ARG_NO_SAVE_ARGS } + { "NO_SAVE_ARGS", FWKNOP_CLI_ARG_NO_SAVE_ARGS }, + { "USE_TOTP", FWKNOP_CLI_ARG_USE_TOTP } }; /* Array to define which conf. variables are critical and should not be @@ -219,7 +221,7 @@ generate_keys(fko_cli_options_t *options) /* Generate the key through libfko */ res = fko_key_gen(options->key_base64, options->key_len, options->hmac_key_base64, options->hmac_key_len, - options->hmac_type); + options->hmac_type, options->totp_key_base32, options->totp_key_len); /* Exit upon key generation failure*/ if(res != FKO_SUCCESS) @@ -1294,6 +1296,12 @@ parse_rc_param(fko_cli_options_t *options, const char *var_name, char * val) options->resolve_http_only = 1; else; } + else if (var->pos == FWKNOP_CLI_ARG_USE_TOTP) + { + if (is_yes_str(val)) + options->use_totp = 1; + else; + } /* avoid saving .fwknop.run by default */ else if (var->pos == FWKNOP_CLI_ARG_NO_SAVE_ARGS) { @@ -1418,6 +1426,7 @@ add_single_var_to_rc(FILE* fhandle, short var_pos, fko_cli_options_t *options) case FWKNOP_CLI_ARG_KEY_HMAC: strlcpy(val, options->hmac_key, sizeof(val)); break; + /* TODO: does TOTP need FWKNOP_CLI_ARG_TOTP stuff as well? */ case FWKNOP_CLI_ARG_HMAC_DIGEST_TYPE : hmac_digest_inttostr(options->hmac_type, val, sizeof(val)); break; @@ -2470,6 +2479,9 @@ config_init(fko_cli_options_t *options, int argc, char **argv) options->input_fd = strtol_wrapper(optarg, 0, -1, EXIT_UPON_ERR, &is_err); break; + case USE_TOTP: + options->use_totp = 1; + break; default: usage(); exit(EXIT_FAILURE); @@ -2493,6 +2505,7 @@ config_init(fko_cli_options_t *options, int argc, char **argv) { add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_RIJNDAEL_BASE64, &var_bitmask); add_var_to_bitmask(FWKNOP_CLI_ARG_KEY_HMAC_BASE64, &var_bitmask); + /* TODO: TOTP?*/ } else; @@ -2657,6 +2670,7 @@ usage(void) " --time-offset-plus Add time to outgoing SPA packet timestamp.\n" " --time-offset-minus Subtract time from outgoing SPA packet\n" " timestamp.\n" + " --totp Use TOTP.\n" ); return; diff --git a/client/fwknop.c b/client/fwknop.c index bcd2c99f..e67aae0d 100644 --- a/client/fwknop.c +++ b/client/fwknop.c @@ -40,6 +40,8 @@ */ static int get_keys(fko_ctx_t ctx, fko_cli_options_t *options, char *key, int *key_len, char *hmac_key, int *hmac_key_len); +static int get_totp(fko_ctx_t ctx, fko_cli_options_t *options, + char *totp); static void errmsg(const char *msg, const int err); static int prev_exec(fko_cli_options_t *options, int argc, char **argv); static int get_save_file(char *args_save_file); @@ -390,6 +392,19 @@ main(int argc, char **argv) key_len = 16; } + if(options.use_totp) + { + char *temp = malloc(6); + get_totp(ctx, &options, temp); + fko_set_totp(ctx, temp); + temp = NULL; + free(temp); + } + else + { + log_msg(LOG_VERBOSITY_NORMAL, "Not using TOTP"); + } + /* Finalize the context data (encrypt and encode the SPA data) */ res = fko_spa_data_final(ctx, key, key_len, hmac_key, hmac_key_len); @@ -1239,6 +1254,30 @@ get_keys(fko_ctx_t ctx, fko_cli_options_t *options, return 1; } +/* Prompt for and receive a TOTP +*/ +static int +get_totp(fko_ctx_t ctx, fko_cli_options_t *options, + char *totp) +{ + char *key_tmp = NULL; + if (options->use_totp) + { + key_tmp = getpasswd("Enter TOTP: ", options->input_fd); + if(key_tmp == NULL) + { + log_msg(LOG_VERBOSITY_ERROR, "[*] get_totp() error."); + return 0; + } + /* TODO: ensure the length of the input */ + memcpy(totp, key_tmp, 6); + } + else + { + log_msg(LOG_VERBOSITY_ERROR, "[-] Could not read TOTP from user."); + } +} + /* Display an FKO error message. */ void diff --git a/client/fwknop_common.h b/client/fwknop_common.h index 52134de7..8da840f1 100644 --- a/client/fwknop_common.h +++ b/client/fwknop_common.h @@ -86,6 +86,7 @@ typedef struct fko_cli_options char args_save_file[MAX_PATH_LEN]; int no_save_args; int use_hmac; + int use_totp; char spa_server_str[MAX_SERVER_STR_LEN]; /* may be a hostname */ char allow_ip_str[MAX_IPV4_STR_LEN]; char spoof_ip_src_str[MAX_IPV4_STR_LEN]; @@ -112,6 +113,13 @@ typedef struct fko_cli_options int have_hmac_key; int have_hmac_base64_key; int hmac_type; + /* TOTP key is never read from the .fwknoprc file, but this is needed for key generation + */ + char totp_key[MAX_KEY_LEN+1]; + char totp_key_base32[MAX_KEY_LEN+1]; + int totp_key_len; + int have_totp_key; + int have_base64_totp_key; /* NAT access */ diff --git a/common/fko_util.c b/common/fko_util.c index 77e89adb..860e7e22 100644 --- a/common/fko_util.c +++ b/common/fko_util.c @@ -706,6 +706,28 @@ append_msg_to_buf(char *buf, size_t buf_size, const char* msg, ...) return bytes_written; } +/* Determine if a buffer contains only characters from the base32 + * encoding set +*/ +int +is_base32(const unsigned char * const buf, const unsigned short int len) +{ + unsigned short int i; + int rv = 1; + + for(i=0; i 0) { + while (bytes_remaining) { + i_bits = (i_bits << 8) + *in++; + bytes_remaining--; + i_shift += 8; + + /* concat 5 8-bit input groups */ + do { + *dst++ = b32[(i_bits << 5 >> i_shift) & 0x1f]; + i_shift -= 5; + } while (i_shift > 5 || (bytes_remaining == 0 && i_shift > 0)); + } + } + + *dst = '\0'; + + return(dst - out); +} + +int +b32_decode(const char *in, unsigned char *out) +{ + int i, bits; + unsigned long v; + unsigned char *dst = out; + + v = 0, bits = 0; + for (i = 0; in[i] && in[i] != '='; i++) { + unsigned int index= in[i]-50; + + if (index>=(sizeof(map3)/sizeof(map3[0])) || map3[index] == 0xff) + return(-1); + + v = (v << 5) + map3[index]; + bits += 5; + + /* a bit of a hacky way, but it works */ + while (bits >= 8) { + *dst++ = (v >> (bits - 8)) & 0xff; + bits -= 8; + } + } + + *dst = '\0'; + + return(dst - out); +} + +#ifdef HAVE_C_UNIT_TESTS /* LCOV_EXCL_START */ +DECLARE_UTEST(test_base32_encode, "test base32 encoding functions") +{ + char test_str[32] = {0}; + char test_out[32] = {0}; + char expected_out1[32] = {0}; + char expected_out2[32] = {0}; + char expected_out3[32] = {0}; + char expected_out4[32] = {0}; + char expected_out5[32] = {0}; + char expected_out6[32] = {0}; + char expected_out7[32] = {0}; + + strcpy(expected_out1, ""); + strcpy(expected_out2, "MY"); + strcpy(expected_out3, "MZXQ"); + strcpy(expected_out4, "MZXW6"); + strcpy(expected_out5, "MZXW6YQ"); + strcpy(expected_out6, "MZXW6YTB"); + strcpy(expected_out7, "MZXW6YTBOI"); + + strcpy(test_str, ""); + b32_encode((unsigned char *)test_str, test_out, strlen(test_str)); + CU_ASSERT(strcmp(test_out, expected_out1) == 0); + + strcpy(test_str, "f"); + b32_encode((unsigned char *)test_str, test_out, strlen(test_str)); + CU_ASSERT(strcmp(test_out, expected_out2) == 0); + + strcpy(test_str, "fo"); + b32_encode((unsigned char *)test_str, test_out, strlen(test_str)); + CU_ASSERT(strcmp(test_out, expected_out3) == 0); + + strcpy(test_str, "foo"); + b32_encode((unsigned char *)test_str, test_out, strlen(test_str)); + CU_ASSERT(strcmp(test_out, expected_out4) == 0); + + strcpy(test_str, "foob"); + b32_encode((unsigned char *)test_str, test_out, strlen(test_str)); + CU_ASSERT(strcmp(test_out, expected_out5) == 0); + + strcpy(test_str, "fooba"); + b32_encode((unsigned char *)test_str, test_out, strlen(test_str)); + CU_ASSERT(strcmp(test_out, expected_out6) == 0); + + strcpy(test_str, "foobar"); + b32_encode((unsigned char *)test_str, test_out, strlen(test_str)); + CU_ASSERT(strcmp(test_out, expected_out7) == 0); + +} + +DECLARE_UTEST(test_base32_decode, "test base32 decoding functions") +{ + char test_str[32] = {0}; + char test_out[32] = {0}; + char expected_out1[32] = {0}; + char expected_out2[32] = {0}; + char expected_out3[32] = {0}; + char expected_out4[32] = {0}; + char expected_out5[32] = {0}; + char expected_out6[32] = {0}; + char expected_out7[32] = {0}; + + strcpy(expected_out1, ""); + strcpy(expected_out2, "f"); + strcpy(expected_out3, "fo"); + strcpy(expected_out4, "foo"); + strcpy(expected_out5, "foob"); + strcpy(expected_out6, "fooba"); + strcpy(expected_out7, "foobar"); + + strcpy(test_str, ""); + b32_decode(test_str, (unsigned char *)test_out); + CU_ASSERT(strcmp(test_out, expected_out1) == 0); + + strcpy(test_str, "MY"); + b32_decode(test_str, (unsigned char *)test_out); + CU_ASSERT(strcmp(test_out, expected_out2) == 0); + + strcpy(test_str, "MZXQ"); + b32_decode(test_str, (unsigned char *)test_out); + CU_ASSERT(strcmp(test_out, expected_out3) == 0); + + strcpy(test_str, "MZXW6"); + b32_decode(test_str, (unsigned char *)test_out); + CU_ASSERT(strcmp(test_out, expected_out4) == 0); + + strcpy(test_str, "MZXW6YQ"); + b32_decode(test_str, (unsigned char *)test_out); + CU_ASSERT(strcmp(test_out, expected_out5) == 0); + + strcpy(test_str, "MZXW6YTB"); + b32_decode(test_str, (unsigned char *)test_out); + CU_ASSERT(strcmp(test_out, expected_out6) == 0); + + strcpy(test_str, "MZXW6YTBOI"); + b32_decode(test_str, (unsigned char *)test_out); + CU_ASSERT(strcmp(test_out, expected_out7) == 0); +} + +int register_base32_test(void) +{ + ts_init(&TEST_SUITE(base32_test), TEST_SUITE_DESCR(base32_test), NULL, NULL); + ts_add_utest(&TEST_SUITE(base32_test), UTEST_FCT(test_base32_encode), UTEST_DESCR(test_base32_encode)); + ts_add_utest(&TEST_SUITE(base32_test), UTEST_FCT(test_base32_decode), UTEST_DESCR(test_base32_decode)); + + return register_ts(&TEST_SUITE(base32_test)); +} +#endif /* LCOV_EXCL_STOP */ +/***EOF***/ diff --git a/lib/base32.h b/lib/base32.h new file mode 100644 index 00000000..76a02474 --- /dev/null +++ b/lib/base32.h @@ -0,0 +1,40 @@ +/** + * \file lib/base32.h + * + * \brief Header for the fwknop base32.c + */ + +/* Fwknop is developed primarily by the people listed in the file 'AUTHORS'. + * Copyright (C) 2009-2015 fwknop developers and contributors. For a full + * list of contributors, see the file 'CREDITS'. + * + * License (GNU General Public License): + * + * This library 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 2 + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + * + ***************************************************************************** +*/ +#ifndef BASE32_H +#define BASE32_H 1 + +/* Prototypes +*/ +int b32_encode(unsigned char *in, char *out, int in_len); +int b32_decode(const char *in, unsigned char *out); + +#endif /* BASE32_H */ + +/***EOF***/ \ No newline at end of file diff --git a/lib/fko.h b/lib/fko.h index c070fb37..ec75266e 100644 --- a/lib/fko.h +++ b/lib/fko.h @@ -688,6 +688,33 @@ DLL_API const char* fko_errstr(const int err_code); */ DLL_API int fko_encryption_type(const char * const enc_data); +/** + * \brief TODO: TOTP + * + * \param TODO: TOTP + * + * \return TODO: TOTP + */ +DLL_API int fko_totp_from_secret(unsigned int *totp_code, const char * const secret, unsigned long *timestamp, char *time_step); + +/** + * \brief TODO: TOTP + * + * \param TODO: TOTP + * + * \return TODO: TOTP + */ +DLL_API int fko_set_totp(fko_ctx_t ctx, const char * const totp_code); + +/** + * \brief TODO: TOTP + * + * \param TODO: TOTP + * + * \return TODO: TOTP + */ +DLL_API int fko_get_totp(fko_ctx_t ctx, char **totp_code); + /** * \brief generates random keys * @@ -701,7 +728,38 @@ DLL_API int fko_encryption_type(const char * const enc_data); */ DLL_API int fko_key_gen(char * const key_base64, const int key_len, char * const hmac_key_base64, const int hmac_key_len, - const int hmac_type); + const int hmac_type, char * const totp_key_base32, const int totp_key_len); + +/** + * \brief Encodes text or binary data into base32 + * + * Function takes text or binary data and returns a base32 encoded string. + * This implements base32 encoding as per rfc 4648. + * (This is not the url safe encoding scheme) + * + * \param in Pointer to input data. May be text or binary data + * \param out Pointer to the base32 encoded data + * \param in_length Size in bytes of the input + * + * \return Returns length of base32 encoded output + * \todo add CUnit test of base32 encoding: https://tools.ietf.org/html/rfc4648 + */ +DLL_API int fko_base32_encode(unsigned char * const in, char * const out, int in_len); + +/** + * \brief Decodes base32 into text or binary data + * + * Function takes a base32 encoded string and returns the resulting text or binary data + * This implements base32 decoding as per rfc 4648. + * (This is not the url safe encoding scheme) + * + * \param in Pointer to input data. Must be base32 encoded + * \param out Pointer to the resulting data + * + * \return Returns length in bytes of decoded output + * \todo add CUnit test of base32 decoding: https://tools.ietf.org/html/rfc4648 + */ +DLL_API int fko_base32_decode(const char * const in, unsigned char *out); /** * \brief Encodes text or binary data into base64 @@ -1403,6 +1461,7 @@ int register_ts_digest_test(void); int register_ts_aes_test(void); int register_utils_test(void); int register_base64_test(void); +int register_base32_test(void); #endif #endif /* FKO_H */ diff --git a/lib/fko_context.h b/lib/fko_context.h index 806a7874..d6fda49e 100644 --- a/lib/fko_context.h +++ b/lib/fko_context.h @@ -74,6 +74,7 @@ struct fko_context { char *nat_access; char *server_auth; unsigned int client_timeout; + char *totp; /*@}*/ /** \name FKO SPA user-settable message encoding types */ /*@{*/ diff --git a/lib/fko_decode.c b/lib/fko_decode.c index 0d46b857..31cfc219 100644 --- a/lib/fko_decode.c +++ b/lib/fko_decode.c @@ -33,7 +33,7 @@ #include "base64.h" #include "digest.h" -#define FIELD_PARSERS 9 +#define FIELD_PARSERS 10 /* Char used to separate SPA fields in an SPA packet */ #define SPA_FIELD_SEPARATOR ":" @@ -345,6 +345,25 @@ parse_server_auth(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx) return FKO_SUCCESS; } +static int +parse_totp(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx) +{ + /* static size for TOTP */ + *t_size = 6; + + if(ctx->totp != NULL) + free(ctx->totp); + + ctx->totp = malloc(*t_size+1); + if(ctx->totp == NULL) + return(FKO_ERROR_MEMORY_ALLOCATION); + + strlcpy(ctx->totp, *ndx, *t_size+1); + + *ndx += *t_size+1; + return FKO_SUCCESS; +} + static int parse_client_timeout(char *tbuf, char **ndx, int *t_size, fko_ctx_t ctx) { @@ -542,6 +561,7 @@ fko_decode_spa_data(fko_ctx_t ctx) parse_msg_type, /* SPA msg type */ parse_msg, /* SPA msg string */ parse_nat_msg, /* SPA NAT msg string */ + parse_totp, /* optional TOTP field */ parse_server_auth, /* optional server authentication method */ parse_client_timeout /* client defined timeout */ }; diff --git a/lib/fko_encode.c b/lib/fko_encode.c index 9bdbde40..ffee290e 100644 --- a/lib/fko_encode.c +++ b/lib/fko_encode.c @@ -180,6 +180,13 @@ fko_encode_spa_data(fko_ctx_t ctx) } } + if(ctx->totp != NULL) + { + offset = strlen(tbuf); + snprintf(((char*)tbuf+offset), FKO_ENCODE_TMP_BUF_SIZE - offset, + ":%s", ctx->totp); + } + /* If we have a server_auth field set. Add it here. * */ diff --git a/lib/fko_funcs.c b/lib/fko_funcs.c index ffb77e08..04c2feec 100644 --- a/lib/fko_funcs.c +++ b/lib/fko_funcs.c @@ -31,7 +31,9 @@ #include "fko.h" #include "cipher_funcs.h" #include "base64.h" +#include "base32.h" #include "digest.h" +#include "totp.h" /* Initialize an fko context. */ @@ -301,6 +303,9 @@ fko_destroy(fko_ctx_t ctx) if(ctx->server_auth != NULL) free(ctx->server_auth); + + if(ctx->totp != NULL) + free(ctx->totp); if(ctx->digest != NULL) if(zero_free(ctx->digest, ctx->digest_len) != FKO_SUCCESS) @@ -365,18 +370,20 @@ fko_destroy(fko_ctx_t ctx) return(zero_free_rv); } -/* Generate Rijndael and HMAC keys from /dev/random and base64 +/* Generate Rijndael, HMAC and TOTP keys from /dev/random and base64 * encode them */ int fko_key_gen(char * const key_base64, const int key_len, char * const hmac_key_base64, const int hmac_key_len, - const int hmac_type) + const int hmac_type, char * const totp_key_base32, const int totp_key_len) { unsigned char key[RIJNDAEL_MAX_KEYSIZE]; unsigned char hmac_key[SHA512_BLOCK_LEN]; + unsigned char totp_key[TOTP_SECRET_LEN]; int klen = key_len; int hmac_klen = hmac_key_len; + int totp_klen = totp_key_len; int b64_len = 0; if(key_len == FKO_DEFAULT_KEY_LEN) @@ -397,14 +404,23 @@ fko_key_gen(char * const key_base64, const int key_len, hmac_klen = SHA512_BLOCK_LEN; } + if(totp_key_len == FKO_DEFAULT_KEY_LEN) + { + totp_klen = TOTP_SECRET_LEN; + } + if((klen < 1) || (klen > RIJNDAEL_MAX_KEYSIZE)) return(FKO_ERROR_INVALID_DATA_FUNCS_GEN_KEYLEN_VALIDFAIL); if((hmac_klen < 1) || (hmac_klen > SHA512_BLOCK_LEN)) return(FKO_ERROR_INVALID_DATA_FUNCS_GEN_HMACLEN_VALIDFAIL); + if((totp_key < 1) || (totp_klen > TOTP_SECRET_LEN)) + return(FKO_ERROR_INVALID_DATA_FUNCS_GEN_KEYLEN_VALIDFAIL); + get_random_data(key, klen); get_random_data(hmac_key, hmac_klen); + get_random_data(totp_key, totp_klen); b64_len = b64_encode(key, key_base64, klen); if(b64_len < klen) @@ -414,9 +430,27 @@ fko_key_gen(char * const key_base64, const int key_len, if(b64_len < hmac_klen) return(FKO_ERROR_INVALID_DATA_FUNCS_GEN_HMAC_ENCODEFAIL); + b64_len = b32_encode(totp_key, totp_key_base32, totp_klen); + if(b64_len < totp_klen) + return(FKO_ERROR_INVALID_DATA_FUNCS_GEN_KEY_ENCODEFAIL); + return(FKO_SUCCESS); } +/* Provide an FKO wrapper around base64 encode/decode functions +*/ +int +fko_base32_encode(unsigned char * const in, char * const out, int in_len) +{ + return b32_encode(in, out, in_len); +} + +int +fko_base32_decode(const char * const in, unsigned char *out) +{ + return b32_decode(in, out); +} + /* Provide an FKO wrapper around base64 encode/decode functions */ int diff --git a/lib/fko_utests.c b/lib/fko_utests.c index 707ae890..9964d5f1 100644 --- a/lib/fko_utests.c +++ b/lib/fko_utests.c @@ -22,6 +22,7 @@ static void register_test_suites(void) register_ts_aes_test(); register_utils_test(); register_base64_test(); + register_base32_test(); } /* The main() function for setting up and running the tests. diff --git a/lib/totp.c b/lib/totp.c new file mode 100644 index 00000000..e6caa025 --- /dev/null +++ b/lib/totp.c @@ -0,0 +1,85 @@ +#include "totp.h" + +uint32_t +dynamic_truncation(unsigned char* hmac_result) +{ + uint32_t bin_code, offset; + + /* RFC4226 p7-8 */ + offset = hmac_result[HMAC_LENGTH - 1] & 0xf; + + bin_code = (hmac_result[offset] & 0x7f) << 24 + | (hmac_result[offset+1] & 0xff) << 16 + | (hmac_result[offset+2] & 0xff) << 8 + | (hmac_result[offset+3] & 0xff); + + return bin_code; +} + +/* Used by the client to set the TOTP value inside the context +*/ +int +fko_set_totp(fko_ctx_t ctx, const char * const totp_code) +{ + /* Must be initialized + */ + if(!CTX_INITIALIZED(ctx)) + return FKO_ERROR_CTX_NOT_INITIALIZED; + + /* Clear previous buffer + */ + if(ctx->totp != NULL) + free(ctx->totp); + + /* TODO: need to verify the memory allocation */ + ctx->totp = calloc(1, DIGITS+1); + if(ctx->totp == NULL) + return(FKO_ERROR_MEMORY_ALLOCATION); + + ctx->totp = strdup(totp_code); + return(FKO_SUCCESS); +} + +/* Used by the server to get the TOTP value from the context +*/ +int +fko_get_totp(fko_ctx_t ctx, char **totp_code) +{ + /* Must be initialized + */ + if(!CTX_INITIALIZED(ctx)) + return(FKO_ERROR_CTX_NOT_INITIALIZED); + + /* TODO: Is this necessary?*/ + // if(totp_code == NULL) + // return(FKO_ERROR_INVALID_DATA); + + *totp_code = ctx->totp; + + return(FKO_SUCCESS); +} + +/* Calculate TOTP based on initial secret and current timestamp +*/ +int +fko_totp_from_secret(uint32_t *totp_code, const char * const secret, uint64_t *timestamp, char *time_step) +{ + // HMAC-SHA1 result buffer + uint8_t hmac_result[HMAC_LENGTH] = {0}; + + // configure timestamp (T) + uint64_t T = (uint64_t)floor((*timestamp - T0) / X) - *time_step; + char time_buf[TIME_LEN] = {0}; + + for (char i = 1; i <= TIME_LEN; i++) + { + time_buf[TIME_LEN - i] = (char)(((T >> 4) % 0x10) << 4 | (T % 0x10)); + T >>= 8; + } + + if(hmac_sha1((const char *)time_buf, TIME_LEN, hmac_result, secret, TOTP_SECRET_LEN) != FKO_SUCCESS) + return 0; + + *totp_code = dynamic_truncation(hmac_result) % (int)floor(pow(10.0, DIGITS)); + return 1; +} diff --git a/lib/totp.h b/lib/totp.h new file mode 100644 index 00000000..47d8105e --- /dev/null +++ b/lib/totp.h @@ -0,0 +1,23 @@ +#ifndef __TOTP_H__ +#define __TOTP_H__ + +// libfko imports +#include "fko.h" +#include "fko_common.h" +#include "hmac.h" + +// others +#include +#include +#include +#include +#include + +#define TOTP_SECRET_LEN 20 +#define HMAC_LENGTH 20 +#define TIME_LEN 8 +#define DIGITS 6 // OTP digits +#define X 30 // default time step +#define T0 0 // default value + +#endif /* __TOTP_H__ */ \ No newline at end of file diff --git a/server/access.c b/server/access.c index 9b74e520..7a999163 100644 --- a/server/access.c +++ b/server/access.c @@ -156,6 +156,46 @@ add_acc_group(char **group_var, gid_t *gid_var, return; } +/* Decode base32 encoded string into access entry +*/ +static void +add_acc_b32_string(char **var, int *len, const char *val, FILE *file_ptr, + fko_srv_options_t *opts) +{ + if(var == NULL) + { + log_msg(LOG_ERR, "[*] add_acc_b32_string() called with NULL variable"); + if(file_ptr != NULL) + fclose(file_ptr); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + } + + if(*var != NULL) + free(*var); + + if((*var = strdup(val)) == NULL) + { + log_msg(LOG_ERR, + "[*] Fatal memory allocation error adding access list entry: %s", *var + ); + if(file_ptr != NULL) + fclose(file_ptr); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + } + memset(*var, 0x0, strlen(val)); + *len = fko_base32_decode(val, (unsigned char *) *var); + + if (*len < 0) + { + log_msg(LOG_ERR, + "[*] base32 decoding returned error for: %s", *var + ); + if(file_ptr != NULL) + fclose(file_ptr); + clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE); + } + return; +} /* Decode base64 encoded string into access entry */ static void @@ -915,6 +955,18 @@ free_acc_stanza_data(acc_stanza_t *acc) free(acc->hmac_key_base64); } + if(acc->totp_key != NULL) + { + zero_buf_wrapper(acc->totp_key, strlen(acc->totp_key)); + free(acc->totp_key); + } + + if(acc->totp_key_base32 != NULL) + { + zero_buf_wrapper(acc->totp_key_base32, strlen(acc->totp_key_base32)); + free(acc->totp_key_base32); + } + if(acc->cmd_sudo_exec_user != NULL) free(acc->cmd_sudo_exec_user); @@ -1273,8 +1325,9 @@ acc_data_is_valid(fko_srv_options_t *opts, if(((acc->key == NULL || acc->key_len == 0) && ((acc->gpg_decrypt_pw == NULL || !strlen(acc->gpg_decrypt_pw)) - && acc->gpg_allow_no_pw == 0)) - || (acc->use_rijndael == 0 && acc->use_gpg == 0 && acc->gpg_allow_no_pw == 0)) + && acc->gpg_allow_no_pw == 0) + && (acc->totp_key == NULL || acc->totp_key_len == 0)) + || (acc->use_rijndael == 0 && acc->use_gpg == 0 && acc->gpg_allow_no_pw == 0 && acc->use_totp == 0)) { log_msg(LOG_ERR, "[*] No keys found for access stanza source: '%s'", acc->source @@ -1732,6 +1785,30 @@ parse_access_file(fko_srv_options_t *opts, char *access_filename, int *depth) add_acc_string(&(curr_acc->hmac_key), val, file_ptr, opts); curr_acc->hmac_key_len = strlen(curr_acc->hmac_key); } + /* TOTP key base32 */ + else if(CONF_VAR_IS(var, "TOTP_KEY_BASE32")) + { + /* TODO: is_base32 */ + if (! is_base32((unsigned char *) val, strlen(val))) + { + log_msg(LOG_ERR, + "[*] TOTP_KEY_BASE32 argument '%s' doesn't look like base32-encoded data.", + val); + fclose(file_ptr); + return EXIT_FAILURE; + } + add_acc_string(&(curr_acc->totp_key_base32), val, file_ptr, opts); + add_acc_b32_string(&(curr_acc->totp_key), &(curr_acc->totp_key_len), + curr_acc->totp_key_base32, file_ptr, opts); + add_acc_bool(&(curr_acc->use_totp), "Y"); + } + /* TOTP key */ + else if(CONF_VAR_IS(var, "TOTP_KEY")) + { + add_acc_string(&(curr_acc->totp_key), val, file_ptr, opts); + curr_acc->totp_key_len = strlen(curr_acc->totp_key); + add_acc_bool(&(curr_acc->use_totp), "Y"); + } else if(CONF_VAR_IS(var, "FW_ACCESS_TIMEOUT")) { curr_acc->fw_access_timeout = strtol_wrapper(val, 0, diff --git a/server/config_init.c b/server/config_init.c index 47fd706e..bcb406dc 100644 --- a/server/config_init.c +++ b/server/config_init.c @@ -222,6 +222,7 @@ generate_keys(fko_srv_options_t *options) { char key_base64[MAX_B64_KEY_LEN+1]; char hmac_key_base64[MAX_B64_KEY_LEN+1]; + char totp_key_base32[32+1]; /* TODO: make const */ FILE *key_gen_file_ptr = NULL; int res; @@ -240,11 +241,12 @@ generate_keys(fko_srv_options_t *options) /* Zero out the key buffers */ memset(key_base64, 0x00, sizeof(key_base64)); memset(hmac_key_base64, 0x00, sizeof(hmac_key_base64)); + memset(totp_key_base32, 0x00, sizeof(totp_key_base32)); /* Generate the key through libfko */ res = fko_key_gen(key_base64, options->key_len, hmac_key_base64, options->hmac_key_len, - options->hmac_type); + options->hmac_type, totp_key_base32, options->totp_key_len); if(res != FKO_SUCCESS) { @@ -261,16 +263,16 @@ generate_keys(fko_srv_options_t *options) options->key_gen_file, strerror(errno)); clean_exit(options, NO_FW_CLEANUP, EXIT_FAILURE); } - fprintf(key_gen_file_ptr, "KEY_BASE64: %s\nHMAC_KEY_BASE64: %s\n", - key_base64, hmac_key_base64); + fprintf(key_gen_file_ptr, "KEY_BASE64: %s\nHMAC_KEY_BASE64: %s\nTOTP_KEY_BASE32: %s\n", + key_base64, hmac_key_base64, totp_key_base32); fclose(key_gen_file_ptr); fprintf(stdout, "[+] Wrote Rijndael and HMAC keys to: %s", options->key_gen_file); } else { - fprintf(stdout, "KEY_BASE64: %s\nHMAC_KEY_BASE64: %s\n", - key_base64, hmac_key_base64); + fprintf(stdout, "KEY_BASE64: %s\nHMAC_KEY_BASE64: %s\nTOTP_KEY_BASE32: %s\n", + key_base64, hmac_key_base64, totp_key_base32); } clean_exit(options, NO_FW_CLEANUP, EXIT_SUCCESS); } diff --git a/server/fwknopd_common.h b/server/fwknopd_common.h index 837aef8a..429e144a 100644 --- a/server/fwknopd_common.h +++ b/server/fwknopd_common.h @@ -403,6 +403,10 @@ typedef struct acc_stanza int hmac_key_len; char *hmac_key_base64; int hmac_type; + char *totp_key; + int totp_key_len; + char *totp_key_base32; + unsigned char use_totp; unsigned char use_rijndael; int fw_access_timeout; int max_fw_timeout; @@ -624,6 +628,7 @@ typedef struct spa_data char pkt_destination_ip[MAX_IPV4_STR_LEN]; char spa_message_remain[1024]; /* --DSS FIXME: arbitrary bounds */ char *nat_access; + char *totp; char *server_auth; unsigned int client_timeout; unsigned int fw_access_timeout; @@ -680,6 +685,7 @@ typedef struct fko_srv_options int key_len; int hmac_key_len; int hmac_type; + int totp_key_len; #if USE_FILE_CACHE struct digest_cache_list *digest_cache; /* In-memory digest cache list */ diff --git a/server/incoming_spa.c b/server/incoming_spa.c index d762d2bd..cc22ac36 100644 --- a/server/incoming_spa.c +++ b/server/incoming_spa.c @@ -292,6 +292,10 @@ get_spa_data_fields(fko_ctx_t ctx, spa_data_t *spdat) if(res != FKO_SUCCESS) return(res); + res = fko_get_totp(ctx, &(spdat->totp)); + if(res != FKO_SUCCESS) + return(res); + res = fko_get_spa_server_auth(ctx, &(spdat->server_auth)); if(res != FKO_SUCCESS) return(res); @@ -583,13 +587,66 @@ check_mode_ctx(spa_data_t *spadat, fko_ctx_t *ctx, int attempted_decrypt, return 1; } +static int +handle_totp_enc(acc_stanza_t *acc, spa_pkt_info_t *spa_pkt, + spa_data_t *spadat, fko_ctx_t *ctx, int *attempted_decrypt, + int *cmd_exec_success, const int enc_type, const int stanza_num, + int *res) +{ + /* TODO: look into || acc->enable_cmd_exec */ + if (acc->use_totp && enc_type == FKO_ENCRYPTION_RIJNDAEL) + { + *res = fko_new_with_data(ctx, (char *)spa_pkt->packet_data, + acc->key, acc->key_len, acc->encryption_mode, acc->hmac_key, + acc->hmac_key_len, acc->hmac_type); + *attempted_decrypt = 1; + if(*res == FKO_SUCCESS) + *cmd_exec_success = 1; + } + return 1; +} + +static void +verify_totp(acc_stanza_t *acc, spa_pkt_info_t *spa_pkt, + spa_data_t *spadat, fko_ctx_t *ctx, int *attempted_decrypt, + int *cmd_exec_success, const int enc_type, const int stanza_num, + int *res) +{ + /* store final TOTP and current timestamp for TOTP */ + uint32_t totp_code = 0; + uint64_t timestamp = (uint64_t)time(NULL); + char time_step = 0; + if(!fko_totp_from_secret(&totp_code, acc->totp_key, ×tamp, &time_step)) + { + log_msg(LOG_ERR, + "Unexpected error on TOTP generation."); + } + + /* convert TOTP from integer to char buffer + */ + unsigned char totp[6] = {0}; + for (size_t i = 1; i <= 6; i++) + { + totp[6 - i] = (char)('0' + (totp_code % 10)); + totp_code /= 10; + } + + /* compare the client and server calculated TOTPs + */ + if(strncmp(spadat->totp, (const char *)totp, 6) != 0) + *res = FKO_ERROR_UNKNOWN; /* TODO: FKO_TOTP_ERROR */ + + return; +} + + static void handle_rijndael_enc(acc_stanza_t *acc, spa_pkt_info_t *spa_pkt, spa_data_t *spadat, fko_ctx_t *ctx, int *attempted_decrypt, int *cmd_exec_success, const int enc_type, const int stanza_num, int *res) -{ - if(enc_type == FKO_ENCRYPTION_RIJNDAEL || acc->enable_cmd_exec) +{ + if(*cmd_exec_success == 0 && (enc_type == FKO_ENCRYPTION_RIJNDAEL || acc->enable_cmd_exec)) { *res = fko_new_with_data(ctx, (char *)spa_pkt->packet_data, acc->key, acc->key_len, acc->encryption_mode, acc->hmac_key, @@ -1012,8 +1069,8 @@ incoming_spa(fko_srv_options_t *opts) if(acc->use_rijndael) handle_rijndael_enc(acc, spa_pkt, &spadat, &ctx, - &attempted_decrypt, &cmd_exec_success, enc_type, - stanza_num, &res); + &attempted_decrypt, &cmd_exec_success, enc_type, + stanza_num, &res); if(! handle_gpg_enc(acc, spa_pkt, &spadat, &ctx, &attempted_decrypt, cmd_exec_success, enc_type, stanza_num, &res)) @@ -1044,7 +1101,7 @@ incoming_spa(fko_srv_options_t *opts) */ log_msg(LOG_DEBUG, "[%s] (stanza #%d) SPA Decode (res=%i):", spadat.pkt_source_ip, stanza_num, res); - + res = dump_ctx_to_buffer(ctx, dump_buf, sizeof(dump_buf)); if (res == FKO_SUCCESS) log_msg(LOG_DEBUG, "%s", dump_buf); @@ -1076,6 +1133,23 @@ incoming_spa(fko_srv_options_t *opts) continue; } + /* Verify the TOTP from the SPA packet if the access stanza requires it + */ + if(acc->use_totp) + verify_totp(acc, spa_pkt, &spadat, &ctx, + &attempted_decrypt, &cmd_exec_success, enc_type, + stanza_num, &res); + + if(res != FKO_SUCCESS) + { + log_msg(LOG_ERR, + "[%s] (stanza #%d) Incorrect TOTP: %s", + spadat.pkt_source_ip, stanza_num, fko_errstr(res)); + + acc = acc->next; + continue; + } + /* Figure out what our timeout will be. If it is specified in the SPA * data, then use that. If not, try the FW_ACCESS_TIMEOUT from the * access.conf file (if there is one). Otherwise use the default.