From d0ea801724707c50517651955d6659e45d236f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Helleu?= Date: Sat, 27 Oct 2018 11:03:03 +0200 Subject: core: add support of TOTP generation/validation (Time-based One-Time Password) --- src/core/wee-secure.c | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/core/wee-secure.h | 6 ++ src/core/wee-string.c | 157 +++++++++++++++++++++++++++++++++++++++++ src/core/wee-string.h | 2 + 4 files changed, 357 insertions(+) (limited to 'src/core') diff --git a/src/core/wee-secure.c b/src/core/wee-secure.c index c4382d3f9..b9abe62c9 100644 --- a/src/core/wee-secure.c +++ b/src/core/wee-secure.c @@ -25,6 +25,9 @@ #include #include +#include +#include +#include #include #include "weechat.h" @@ -494,6 +497,195 @@ secure_decrypt_data_not_decrypted (const char *passphrase) return num_ok; } +/* + * Generates a Time-based One-Time Password (TOTP), as described + * in the RFC 6238. + * + * Returns: + * 1: OK + * 0: error + */ + +int +secure_totp_generate_internal (const char *secret, int length_secret, + uint64_t moving_factor, int digits, + char *result) +{ + gcry_md_hd_t hd_md; + uint64_t moving_factor_swapped; + unsigned char *ptr_hash; + char hash[20]; + int offset, length; + unsigned long bin_code; + + if (gcry_md_open (&hd_md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC) != 0) + return 0; + + if (gcry_md_setkey (hd_md, secret, length_secret) != 0) + { + gcry_md_close (hd_md); + return 0; + } + + moving_factor_swapped = (moving_factor >> 56) + | ((moving_factor << 40) & 0x00FF000000000000) + | ((moving_factor << 24) & 0x0000FF0000000000) + | ((moving_factor << 8) & 0x000000FF00000000) + | ((moving_factor >> 8) & 0x00000000FF000000) + | ((moving_factor >> 24) & 0x0000000000FF0000) + | ((moving_factor >> 40) & 0x000000000000FF00) + | (moving_factor << 56); + + gcry_md_write (hd_md, + &moving_factor_swapped, sizeof (moving_factor_swapped)); + + ptr_hash = gcry_md_read (hd_md, GCRY_MD_SHA1); + if (!ptr_hash) + { + gcry_md_close (hd_md); + return 0; + } + + memcpy (hash, ptr_hash, sizeof (hash)); + + gcry_md_close (hd_md); + + offset = hash[19] & 0xf; + bin_code = (hash[offset] & 0x7f) << 24 + | (hash[offset+1] & 0xff) << 16 + | (hash[offset+2] & 0xff) << 8 + | (hash[offset+3] & 0xff); + + bin_code %= (unsigned long)(pow (10, digits)); + + length = snprintf (result, digits + 1, "%.*lu", digits, bin_code); + if (length != digits) + return 0; + + return 1; +} + +/* + * Generates a Time-based One-Time Password (TOTP), as described + * in the RFC 6238. + * + * Returns the password as string, NULL if error. + * + * Note: result must be freed after use. + */ + +char * +secure_totp_generate (const char *secret_base32, time_t totp_time, int digits) +{ + char *result, *secret; + int length_secret, rc; + uint64_t moving_factor; + + secret = NULL; + result = NULL; + + if (!secret_base32 || !secret_base32[0] + || (digits < SECURE_TOTP_MIN_DIGITS) + || (digits > SECURE_TOTP_MAX_DIGITS)) + { + goto error; + } + + secret = malloc ((strlen (secret_base32) * 4) + 16 + 1); + if (!secret) + goto error; + + length_secret = string_decode_base32 (secret_base32, secret); + if (length_secret < 0) + goto error; + + result = malloc (digits + 1); + if (!result) + goto error; + + if (totp_time == 0) + totp_time = time (NULL); + + moving_factor = totp_time / 30; + + rc = secure_totp_generate_internal (secret, length_secret, + moving_factor, digits, result); + if (!rc) + goto error; + + free (secret); + + return result; + +error: + if (secret) + free (secret); + if (result) + free (result); + return NULL; +} + +/* + * Validates a Time-based One-Time Password (TOTP). + * + * Returns: + * 1: OTP is OK + * 0: OTP is invalid + */ + +int +secure_totp_validate (const char *secret_base32, time_t totp_time, int window, + const char *otp) +{ + char *secret, str_otp[16]; + int length_secret, digits, rc, otp_ok; + uint64_t i, moving_factor; + + secret = NULL; + + if (!secret_base32 || !secret_base32[0] || (window < 0) || !otp || !otp[0]) + goto error; + + digits = strlen (otp); + if ((digits < SECURE_TOTP_MIN_DIGITS) || (digits > SECURE_TOTP_MAX_DIGITS)) + goto error; + + secret = malloc (strlen (secret_base32) + 1); + if (!secret) + goto error; + + length_secret = string_decode_base32 (secret_base32, secret); + if (length_secret < 0) + goto error; + + if (totp_time == 0) + totp_time = time (NULL); + + moving_factor = totp_time / 30; + + otp_ok = 0; + + for (i = moving_factor - window; i <= moving_factor + window; i++) + { + rc = secure_totp_generate_internal (secret, length_secret, + i, digits, str_otp); + if (rc && (strcmp (str_otp, otp) == 0)) + { + otp_ok = 1; + break; + } + } + + free (secret); + + return otp_ok; + +error: + if (secret) + free (secret); + return 0; +} + /* * Initializes secured data. * diff --git a/src/core/wee-secure.h b/src/core/wee-secure.h index 4a2e9dc9d..f20a249fd 100644 --- a/src/core/wee-secure.h +++ b/src/core/wee-secure.h @@ -24,6 +24,8 @@ #define SECURE_SALT_DEFAULT "WeeChat!" #define SECURE_DATA_PASSPHRASE_FLAG "__passphrase__" #define SECURE_SALT_SIZE 8 +#define SECURE_TOTP_MIN_DIGITS 4 +#define SECURE_TOTP_MAX_DIGITS 10 enum t_secure_config_hash_algo { @@ -59,6 +61,10 @@ extern int secure_decrypt_data (const char *buffer, int length_buffer, const char *passphrase, char **decrypted, int *length_decrypted); extern int secure_decrypt_data_not_decrypted (const char *passphrase); +extern char *secure_totp_generate (const char *secret, time_t totp_time, + int digits); +extern int secure_totp_validate (const char *secret, time_t totp_time, + int window, const char *otp); extern int secure_init (); extern void secure_end (); diff --git a/src/core/wee-string.c b/src/core/wee-string.c index 9c525ff6c..8ff1de3de 100644 --- a/src/core/wee-string.c +++ b/src/core/wee-string.c @@ -2775,6 +2775,163 @@ string_decode_base16 (const char *from, char *to) return to_length; } +/* + * Encodes a string in base32. + * + * Argument "length" is number of bytes in "from" to convert (commonly + * strlen(from)). + * + * This function is inspired by: + * https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c + * + * Original copyright: + * + * Copyright 2010 Google Inc. + * Author: Markus Gutschke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Returns length of string in "*to" (it does not count final \0). + */ + +int +string_encode_base32 (const char *from, int length, char *to) +{ + unsigned char base32_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + int count, value, next, bits_left, pad, index; + int length_padding[8] = { 0, 0, 6, 0, 4, 3, 0, 2 }; + + if (!from || !to) + return -1; + + count = 0; + + if (length > 0) + { + value = from[0]; + next = 1; + bits_left = 8; + while ((bits_left > 0) || (next < length)) + { + if (bits_left < 5) + { + if (next < length) + { + value <<= 8; + value |= from[next++] & 0xFF; + bits_left += 8; + } + else + { + pad = 5 - bits_left; + value <<= pad; + bits_left += pad; + } + } + index = 0x1F & (value >> (bits_left - 5)); + bits_left -= 5; + to[count++] = base32_table[index]; + } + } + pad = length_padding[count % 8]; + while (pad > 0) + { + to[count++] = '='; + pad--; + } + to[count] = '\0'; + + return count; +} + +/* + * Decodes a base32 string. + * + * This function is inspired by: + * https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c + * + * Original copyright: + * + * Copyright 2010 Google Inc. + * Author: Markus Gutschke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Returns length of string in "*to" (it does not count final \0). + */ + +int +string_decode_base32 (const char *from, char *to) +{ + const char *ptr_from; + int value, bits_left, count; + unsigned char c; + + if (!from || !to) + return -1; + + ptr_from = from; + value = 0; + bits_left = 0; + count = 0; + + while (ptr_from[0]) + { + c = (unsigned char)ptr_from[0]; + value <<= 5; + if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) + { + c = (c & 0x1F) - 1; + } + else if ((c >= '2') && (c <= '7')) + { + c -= '2' - 26; + } + else if (c == '=') + { + /* padding */ + break; + } + else + { + /* invalid base32 char */ + return -1; + } + value |= c; + bits_left += 5; + if (bits_left >= 8) + { + to[count++] = value >> (bits_left - 8); + bits_left -= 8; + } + ptr_from++; + } + to[count] = '\0'; + + return count; +} + /* * Converts 3 bytes of 8 bits in 4 bytes of 6 bits. */ diff --git a/src/core/wee-string.h b/src/core/wee-string.h index 605aa1598..7e33352c8 100644 --- a/src/core/wee-string.h +++ b/src/core/wee-string.h @@ -107,6 +107,8 @@ extern int string_fprintf (FILE *file, const char *data, ...); extern char *string_format_size (unsigned long long size); extern void string_encode_base16 (const char *from, int length, char *to); extern int string_decode_base16 (const char *from, char *to); +extern int string_encode_base32 (const char *from, int length, char *to); +extern int string_decode_base32 (const char *from, char *to); extern void string_encode_base64 (const char *from, int length, char *to); extern int string_decode_base64 (const char *from, char *to); extern char *string_hex_dump (const char *data, int data_size, -- cgit v1.2.3