Merge changes from 1.2.8-1
Francisco Vilmar Cardoso Ruviaro
1 year, 2 months ago
11 | 11 | mkdir build && cd "$_" |
12 | 12 | cmake -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_TESTING=ON .. |
13 | 13 | make && make install |
14 | ./tests/test_base32encode | |
15 | ./tests/test_base32decode | |
16 | 14 | ./tests/test_cotp |
17 | 15 | |
18 | 16 | ubuntu: |
26 | 24 | mkdir build && cd "$_" |
27 | 25 | cmake -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_TESTING=ON .. |
28 | 26 | make && make install |
29 | ./tests/test_base32encode | |
30 | ./tests/test_base32decode | |
31 | 27 | ./tests/test_cotp |
32 | 28 | |
33 | 29 | workflows: |
35 | 31 | build: |
36 | 32 | jobs: |
37 | 33 | - debian |
38 | - ubuntu⏎ | |
34 | - ubuntu | |
35 |
0 | cmake_minimum_required(VERSION 3.16) | |
1 | project(cotp VERSION "2.0.0" LANGUAGES "C") | |
0 | cmake_minimum_required(VERSION 3.5) | |
1 | project(cotp) | |
2 | 2 | |
3 | 3 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) |
4 | 4 | |
5 | 5 | include(GNUInstallDirs) |
6 | 6 | |
7 | 7 | find_package(PkgConfig REQUIRED) |
8 | find_package(Gcrypt 1.8.0 REQUIRED) | |
8 | find_package(Gcrypt 1.6.0 REQUIRED) | |
9 | pkg_check_modules(BASEENCODE REQUIRED baseencode>=1.0.12) | |
9 | 10 | |
10 | 11 | include_directories(${GCRYPT_INCLUDE_DIR} ${BASEENCODE_INCLUDE_DIRS}) |
11 | 12 | |
14 | 15 | enable_testing() |
15 | 16 | add_subdirectory(tests) |
16 | 17 | |
18 | # set up versioning. | |
19 | set(BUILD_MAJOR "1") | |
20 | set(BUILD_MINOR "2") | |
21 | set(BUILD_VERSION "8") | |
22 | set(BUILD_VERSION ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_VERSION}) | |
23 | ||
17 | 24 | set(CMAKE_C_STANDARD 11) |
18 | 25 | |
19 | set(COTP_HEADERS | |
20 | src/cotp.h | |
21 | ) | |
22 | set(SOURCE_FILES | |
23 | src/otp.c | |
24 | src/utils/base32.c | |
25 | ) | |
26 | set(COTP_HEADERS src/cotp.h) | |
27 | set(SOURCE_FILES src/otp.c) | |
26 | 28 | |
27 | # Set compiler flags for all targets | |
28 | add_compile_options(-Wall -Wextra -O3 -Wformat=2 -Wmissing-format-attribute -fstack-protector-strong -Wundef -Wmissing-format-attribute | |
29 | -fdiagnostics-color=always -Wstrict-prototypes -Wunreachable-code -Wchar-subscripts -Wwrite-strings -Wpointer-arith -Wbad-function-cast | |
30 | -Wcast-align -Werror=format-security -Werror=implicit-function-declaration -Wno-sign-compare -Wno-format-nonliteral -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3) | |
31 | ||
32 | add_link_options(-Wl,--no-add-needed -Wl,--as-needed -Wl,-z,relro,-z,now) | |
29 | set(CMAKE_C_FLAGS "-Wall -Wextra -O3 -Wno-format-truncation -fstack-protector-strong -fPIC") | |
30 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3") | |
33 | 31 | |
34 | 32 | add_library(cotp SHARED ${SOURCE_FILES}) |
35 | 33 | |
36 | target_link_libraries(cotp ${GCRYPT_LIBRARIES}) | |
34 | target_link_libraries(cotp ${GCRYPT_LIBRARIES} ${BASEENCODE_LIBRARIES}) | |
37 | 35 | |
38 | set_target_properties(cotp PROPERTIES VERSION ${CMAKE_PROJECT_VERSION} SOVERSION ${CMAKE_PROJECT_VERSION_MAJOR}) | |
36 | set_target_properties(cotp PROPERTIES VERSION ${BUILD_VERSION} SOVERSION ${BUILD_MAJOR}${BUILD_MINOR}) | |
39 | 37 | |
40 | 38 | set(COTP_LIB_DIR "${CMAKE_INSTALL_LIBDIR}") |
41 | 39 | set(COTP_INC_DIR "${CMAKE_INSTALL_INCLUDEDIR}") |
6 | 6 | C library that generates TOTP and HOTP according to [RFC-6238](https://tools.ietf.org/html/rfc6238) |
7 | 7 | |
8 | 8 | ## Requirements |
9 | - [libbaseencode](https://github.com/paolostivanin/libbaseencode) | |
9 | 10 | - GCC/Clang and CMake to build the library |
10 | 11 | - libgcrypt |
11 | 12 | |
21 | 22 | |
22 | 23 | ## How To Use It |
23 | 24 | ``` |
24 | char *totp = get_totp (const char *base32_encoded_secret, | |
25 | int digits, | |
26 | int period, | |
27 | int algo, | |
28 | cotp_error_t *err); | |
25 | char *totp = get_totp (const char *base32_encoded_secret, int digits, int period, int algo, cotp_error_t *err); | |
26 | free (totp); | |
29 | 27 | |
30 | char *steam_totp = get_steam_totp (const char *secret, | |
31 | int period, | |
32 | cotp_error_t *err); | |
28 | char *steam_totp = get_steam_totp (const char *secret, int period, cotp_error_t *err) | |
33 | 29 | |
34 | char *hotp = get_hotp (const char *base32_encoded_secret, | |
35 | long counter, | |
36 | int digits, | |
37 | int algo, | |
38 | cotp_error_t *err); | |
30 | char *hotp = get_hotp (const char *base32_encoded_secret, long counter, int digits, int algo, cotp_error_t *err); | |
31 | free (hotp); | |
39 | 32 | |
40 | char *totp_at = get_totp_at (const char *base32_encoded_secret, | |
41 | long target_date, | |
42 | int digits, | |
43 | int algo, | |
44 | cotp_error_t *err); | |
33 | char *get_totp_at (const char *base32_encoded_secret, long target_date, int digits, int algo, cotp_error_t *err) | |
45 | 34 | |
46 | int64_t otp_i = otp_to_int (const char *otp, | |
47 | cotp_error_t *err_code); | |
35 | int is_valid = totp_verify (const har *base32_encoded_secret, const char *totp, int digits, int period, int algo, cotp_error_t *err); | |
36 | ||
37 | int is_valid = hotp_verify (const char *base32_encoded_secret, long counter, digits, char *hotp, int algo, cotp_error_t *err); | |
48 | 38 | ``` |
49 | 39 | |
50 | 40 | where: |
53 | 43 | - `digits` is between `3` and `10` inclusive |
54 | 44 | - `period` is between `1` and `120` inclusive |
55 | 45 | - `counter` is a value decided with the server |
56 | - `target_date` is the target date specified as the **unix epoch format in seconds** | |
46 | - `target_date` is the target date specified as the unix epoch format in seconds | |
57 | 47 | - `algo` is either `SHA1`, `SHA256` or `SHA512` |
58 | 48 | |
59 | ## Return values | |
60 | `get_totp`, `get_hotp` and `get_totp_at` return `NULL` if an error occurs and `err` is set to one of the following values: | |
61 | ||
62 | Errors: | |
49 | ## Errors | |
50 | `get_totp`, `get_hotp` and `get_totp_at` return `NULL` if an error occurs and `err` is set accordingly. The following errors are currently supported: | |
63 | 51 | - `GCRYPT_VERSION_MISMATCH`, set if the installed Gcrypt library is too old |
64 | 52 | - `INVALID_B32_INPUT`, set if the given input is not valid base32 text |
65 | 53 | - `INVALID_ALGO`, set if the given algo is not supported by the library |
66 | 54 | - `INVALID_PERIOD`, set if `period` is `<= 0` or `> 120` seconds |
67 | - `INVALID_DIGITS`, set if `digits` is `< 4` or `> 10` | |
68 | - `MEMORY_ALLOCATION_ERROR`, set if an error happened during memory allocation | |
69 | - `INVALID_USER_INPUT`, set if the given input is not valid | |
70 | - `INVALID_COUNTER`, set if `counter` is `< 0` | |
55 | - `INVALID_DIGITS`, set if `digits` is `< 3` or `> 10` | |
71 | 56 | |
72 | All good: | |
73 | - `NO_ERROR`, set if no error occurred | |
74 | - `VALID`, set if the given OTP is valid | |
57 | `totp_verify` and `hotp_verify` can return, in addition to one of the previous code, also the error `INVALID_OTP` if the given OTP doesn't match the computed one. | |
75 | 58 | |
76 | The function `otp_to_int`: | |
77 | * returns `-1` if an error occurs and sets `err` to `INVALID_USER_INPUT`. | |
78 | * warns the user if the leading zero is missing. For example, since the otp string `"012345"` **can't** be returned as the integer `012345` (because it would be interpreted as octal number), the function returns `12345` and sets `err` to `MISSING_LEADING_ZERO`) | |
59 | In case of success, the value returned by `get_totp`, `get_hotp` and `get_totp_at` **must be freed** once no longer needed. | |
79 | 60 | |
80 | In case of success, the value returned by `get_totp`, `get_hotp`, `get_totp_at` and `get_steam_totp` **must be freed** once no longer needed. | |
81 | ||
82 | # Base32 encoding and decoding | |
83 | Since release 2.0.0, libbaseencode has been merged with libcotp. This means that you can now use base32 functions by just including `cotp.h`: | |
84 | ||
85 | ``` | |
86 | char *base32_encode (const uchar *user_data, | |
87 | size_t data_len, | |
88 | cotp_error_t *err_code); | |
89 | ||
90 | uchar *base32_decode (const char *user_data, | |
91 | size_t data_len, | |
92 | cotp_error_t *err_code); | |
93 | ``` | |
94 | ||
95 | where: | |
96 | - `user_data` is the data to be encoded/decoded | |
97 | - `data_len` is the length of the data to be encoded/decoded | |
98 | - `err_code` is where the error is stored | |
99 | ||
100 | `base32_encode` returns `NULL` if an error occurs and `err_code` is set to one of the following values: | |
101 | - `INVALID_USER_INPUT`, set if the given input is not valid | |
102 | - `MEMORY_ALLOCATION_ERROR`, set if an error happened during memory allocation | |
103 | - `INVALID_USER_INPUT`, set if the given input is not valid | |
104 | ||
105 | `base32_decode` returns `NULL` if an error occurs and `err_code` is set to one of the following values: | |
106 | - `INVALID_USER_INPUT`, set if the given input is not valid | |
107 | - `MEMORY_ALLOCATION_ERROR`, set if an error happened during memory allocation | |
108 | - `INVALID_B32_INPUT`, set if the given input is not valid base32 text | |
109 | - `INVALID_USER_INPUT`, set if the given input is not valid | |
110 | ||
111 | Both functions return and empty string if the input is an empty string. In such a case, `err` is set to `EMPTY_STRING`. |
4 | 4 | The following list describes whether a version is eligible or not for security updates. |
5 | 5 | |
6 | 6 | | Version | Supported | EOL | |
7 | |---------| ------------------ |-------------| | |
8 | | 2.0.0 | :heavy_check_mark: | - | | |
9 | | 1.2.x | :heavy_check_mark: | 30-Jun-2023 | | |
7 | | ------- | ------------------ |-------------| | |
8 | | 1.2.x | :heavy_check_mark: | - | | |
10 | 9 | | 1.1.x | :x: | 31-Dec-2021 | |
11 | 10 | | 1.0.x | :x: | 31-Dec-2021 | |
12 | 11 |
4 | 4 | |
5 | 5 | Name: libcotp |
6 | 6 | Description: C library that generates TOTP and HOTP |
7 | Version: @CMAKE_PROJECT_VERSION@ | |
7 | Version: @BUILD_VERSION@ | |
8 | 8 | URL: URL: https://github.com/paolostivanin/libcotp |
9 | 9 | Libs: -L${libdir} -lcotp |
10 | 10 | Cflags: -I${includedir} |
6 | 6 | * SONAME change: libcotp12 -> libcotp2. |
7 | 7 | |
8 | 8 | -- Francisco Vilmar Cardoso Ruviaro <vilmar@debian.org> Tue, 10 Jan 2023 12:36:19 +0000 |
9 | ||
10 | libcotp (1.2.8-1) unstable; urgency=medium | |
11 | ||
12 | * New upstream version 1.2.8. (Closes: #1028305) | |
13 | ||
14 | -- Francisco Vilmar Cardoso Ruviaro <vilmar@debian.org> Mon, 16 Jan 2023 21:01:00 +0000 | |
9 | 15 | |
10 | 16 | libcotp (1.2.7-1) unstable; urgency=medium |
11 | 17 |
0 | 0 | #pragma once |
1 | 1 | #include <gcrypt.h> |
2 | #include <stdint.h> | |
3 | 2 | |
4 | 3 | #define SHA1 GCRY_MD_SHA1 |
5 | 4 | #define SHA256 GCRY_MD_SHA256 |
6 | 5 | #define SHA512 GCRY_MD_SHA512 |
7 | 6 | |
8 | #define MIN_DIGTS 4 | |
9 | #define MAX_DIGITS 10 | |
10 | ||
11 | typedef enum cotp_error { | |
12 | NO_ERROR = 0, | |
13 | VALID, | |
14 | GCRYPT_VERSION_MISMATCH, | |
15 | INVALID_B32_INPUT, | |
16 | INVALID_ALGO, | |
17 | INVALID_DIGITS, | |
18 | INVALID_PERIOD, | |
19 | MEMORY_ALLOCATION_ERROR, | |
20 | INVALID_USER_INPUT, | |
21 | EMPTY_STRING, | |
22 | MISSING_LEADING_ZERO, | |
23 | INVALID_COUNTER | |
7 | typedef enum _cotp_errno { | |
8 | VALID = 0, | |
9 | GCRYPT_VERSION_MISMATCH = 1, | |
10 | INVALID_B32_INPUT = 2, | |
11 | INVALID_ALGO = 3, | |
12 | INVALID_OTP = 4, | |
13 | INVALID_DIGITS = 5, | |
14 | INVALID_PERIOD = 6 | |
24 | 15 | } cotp_error_t; |
25 | ||
26 | typedef unsigned char uchar; | |
27 | 16 | |
28 | 17 | #ifdef __cplusplus |
29 | 18 | extern "C" { |
30 | 19 | #endif |
20 | char *get_hotp (const char *base32_encoded_secret, | |
21 | long counter, | |
22 | int digits, | |
23 | int sha_algo, | |
24 | cotp_error_t *err_code); | |
31 | 25 | |
32 | char *base32_encode (const uchar *user_data, | |
33 | size_t data_len, | |
34 | cotp_error_t *err_code); | |
26 | char *get_totp (const char *base32_encoded_secret, | |
27 | int digits, | |
28 | int period, | |
29 | int sha_algo, | |
30 | cotp_error_t *err_code); | |
35 | 31 | |
36 | uchar *base32_decode (const char *user_data_untrimmed, | |
37 | size_t data_len, | |
38 | cotp_error_t *err_code); | |
32 | char *get_steam_totp (const char *base32_encoded_secret, | |
33 | int period, | |
34 | cotp_error_t *err_code); | |
39 | 35 | |
40 | char *get_hotp (const char *base32_encoded_secret, | |
41 | long counter, | |
42 | int digits, | |
43 | int sha_algo, | |
44 | cotp_error_t *err_code); | |
45 | 36 | |
46 | char *get_totp (const char *base32_encoded_secret, | |
47 | int digits, | |
48 | int period, | |
49 | int sha_algo, | |
50 | cotp_error_t *err_code); | |
37 | char *get_totp_at (const char *base32_encoded_secret, | |
38 | long time, | |
39 | int digits, | |
40 | int period, | |
41 | int sha_algo, | |
42 | cotp_error_t *err_code); | |
51 | 43 | |
52 | char *get_steam_totp (const char *base32_encoded_secret, | |
53 | int period, | |
54 | cotp_error_t *err_code); | |
44 | char *get_steam_totp_at (const char *base32_encoded_secret, | |
45 | long timestamp, | |
46 | int period, | |
47 | cotp_error_t *err_code); | |
55 | 48 | |
56 | char *get_totp_at (const char *base32_encoded_secret, | |
57 | long time, | |
58 | int digits, | |
59 | int period, | |
60 | int sha_algo, | |
61 | cotp_error_t *err_code); | |
49 | int totp_verify (const char *base32_encoded_secret, | |
50 | const char *user_totp, | |
51 | int digits, | |
52 | int period, | |
53 | int sha_algo); | |
62 | 54 | |
63 | char *get_steam_totp_at (const char *base32_encoded_secret, | |
64 | long timestamp, | |
65 | int period, | |
66 | cotp_error_t *err_code); | |
55 | int hotp_verify (const char *base32_encoded_secret, | |
56 | long counter, | |
57 | int digits, | |
58 | const char *user_hotp, | |
59 | int sha_algo); | |
67 | 60 | |
68 | int64_t otp_to_int (const char *otp, | |
69 | cotp_error_t *err_code); | |
70 | ||
61 | ||
71 | 62 | #ifdef __cplusplus |
72 | 63 | } |
73 | 64 | #endif |
1 | 1 | #include <time.h> |
2 | 2 | #include <string.h> |
3 | 3 | #include <gcrypt.h> |
4 | #include <ctype.h> | |
4 | #include <baseencode.h> | |
5 | 5 | #include "cotp.h" |
6 | 6 | |
7 | static int check_gcrypt (void); | |
8 | ||
9 | static char *normalize_secret (const char *K); | |
10 | ||
11 | static char *get_steam_code (const uchar *hmac); | |
12 | ||
13 | static int truncate (const uchar *hmac, | |
14 | int digits_length, | |
15 | int algo); | |
16 | ||
17 | static uchar *compute_hmac (const char *K, | |
18 | long C, | |
19 | int algo); | |
20 | ||
21 | static char *finalize (int digits_length, | |
22 | int tk); | |
23 | ||
24 | static int check_period (int period); | |
25 | ||
26 | static int check_otp_len (int digits_length); | |
27 | ||
28 | static int check_algo (int algo); | |
29 | ||
30 | ||
31 | char * | |
32 | get_hotp (const char *secret, | |
33 | long counter, | |
34 | int digits, | |
35 | int algo, | |
36 | cotp_error_t *err_code) | |
37 | { | |
38 | if (check_gcrypt () == -1) { | |
7 | #define SHA1_DIGEST_SIZE 20 | |
8 | #define SHA256_DIGEST_SIZE 32 | |
9 | #define SHA512_DIGEST_SIZE 64 | |
10 | ||
11 | static long long int DIGITS_POWER[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}; | |
12 | ||
13 | ||
14 | static int | |
15 | check_gcrypt() | |
16 | { | |
17 | if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { | |
18 | if (!gcry_check_version("1.6.0")) { | |
19 | fprintf(stderr, "libgcrypt v1.6.0 and above is required\n"); | |
20 | return -1; | |
21 | } | |
22 | gcry_control(GCRYCTL_DISABLE_SECMEM, 0); | |
23 | gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); | |
24 | } | |
25 | return 0; | |
26 | } | |
27 | ||
28 | ||
29 | static char * | |
30 | normalize_secret (const char *K) | |
31 | { | |
32 | char *nK = calloc (1, strlen (K) + 1); | |
33 | if (nK == NULL) { | |
34 | fprintf (stderr, "Error during memory allocation\n"); | |
35 | return nK; | |
36 | } | |
37 | ||
38 | int i = 0, j = 0; | |
39 | while (K[i] != '\0') { | |
40 | if (K[i] != ' ') { | |
41 | if (K[i] >= 'a' && K[i] <= 'z') { | |
42 | nK[j++] = (char) (K[i] - 32); | |
43 | } else { | |
44 | nK[j++] = K[i]; | |
45 | } | |
46 | } | |
47 | i++; | |
48 | } | |
49 | return nK; | |
50 | } | |
51 | ||
52 | ||
53 | static char * | |
54 | get_steam_code(unsigned const char *hmac) | |
55 | { | |
56 | int offset = (hmac[SHA1_DIGEST_SIZE-1] & 0x0f); | |
57 | ||
58 | // Starting from the offset, take the successive 4 bytes while stripping the topmost bit to prevent it being handled as a signed integer | |
59 | int bin_code = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) | ((hmac[offset + 2] & 0xff) << 8) | ((hmac[offset + 3] & 0xff)); | |
60 | ||
61 | const char steam_alphabet[] = "23456789BCDFGHJKMNPQRTVWXY"; | |
62 | ||
63 | char code[6]; | |
64 | size_t steam_alphabet_len = strlen(steam_alphabet); | |
65 | for (int i = 0; i < 5; i++) { | |
66 | int mod = (int)(bin_code % steam_alphabet_len); | |
67 | bin_code = (int)(bin_code / steam_alphabet_len); | |
68 | code[i] = steam_alphabet[mod]; | |
69 | } | |
70 | code[5] = '\0'; | |
71 | ||
72 | return strdup(code); | |
73 | } | |
74 | ||
75 | ||
76 | static int | |
77 | truncate(unsigned const char *hmac, int digits_length, int algo) | |
78 | { | |
79 | // take the lower four bits of the last byte | |
80 | int offset = hmac[gcry_md_get_algo_dlen (algo) - 1] & 0x0f; | |
81 | ||
82 | // Starting from the offset, take the successive 4 bytes while stripping the topmost bit to prevent it being handled as a signed integer | |
83 | int bin_code = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) | ((hmac[offset + 2] & 0xff) << 8) | ((hmac[offset + 3] & 0xff)); | |
84 | ||
85 | int token = (int)(bin_code % DIGITS_POWER[digits_length]); | |
86 | ||
87 | return token; | |
88 | } | |
89 | ||
90 | ||
91 | static unsigned char * | |
92 | compute_hmac(const char *K, long C, int algo) | |
93 | { | |
94 | baseencode_error_t err; | |
95 | size_t secret_len = (size_t)((strlen(K) + 1.6 - 1) / 1.6); | |
96 | ||
97 | char *normalized_K = normalize_secret (K); | |
98 | if (normalized_K == NULL) { | |
99 | return NULL; | |
100 | } | |
101 | unsigned char *secret = base32_decode(normalized_K, strlen(normalized_K), &err); | |
102 | free (normalized_K); | |
103 | if (secret == NULL) { | |
104 | return NULL; | |
105 | } | |
106 | ||
107 | unsigned char C_reverse_byte_order[8]; | |
108 | int j, i; | |
109 | for (j = 0, i = 7; j < 8 && i >= 0; j++, i--) | |
110 | C_reverse_byte_order[i] = ((unsigned char *) &C)[j]; | |
111 | ||
112 | gcry_md_hd_t hd; | |
113 | gpg_error_t gpg_err = gcry_md_open (&hd, algo, GCRY_MD_FLAG_HMAC); | |
114 | if (gpg_err) { | |
115 | printf("%s\n", "Error while opening the cipher handle."); | |
116 | return NULL; | |
117 | } | |
118 | gpg_err = gcry_md_setkey (hd, secret, secret_len); | |
119 | if (gpg_err) { | |
120 | printf("%s\n", "Error while setting the cipher key."); | |
121 | gcry_md_close (hd); | |
122 | return NULL; | |
123 | } | |
124 | gcry_md_write (hd, C_reverse_byte_order, sizeof(C_reverse_byte_order)); | |
125 | gcry_md_final (hd); | |
126 | ||
127 | unsigned char * hmac_tmp = gcry_md_read (hd, algo); | |
128 | if (hmac_tmp == NULL) { | |
129 | fprintf(stderr, "Error getting digest\n"); | |
130 | gcry_md_close (hd); | |
131 | return NULL; | |
132 | } | |
133 | ||
134 | size_t dlen = gcry_md_get_algo_dlen(algo); | |
135 | unsigned char *hmac = malloc (dlen); | |
136 | if (hmac == NULL) { | |
137 | perror("Error allocating memory"); | |
138 | gcry_md_close (hd); | |
139 | return NULL; | |
140 | } | |
141 | memcpy (hmac, hmac_tmp, dlen); | |
142 | ||
143 | free (secret); | |
144 | ||
145 | gcry_md_close (hd); | |
146 | ||
147 | return hmac; | |
148 | } | |
149 | ||
150 | ||
151 | static char * | |
152 | finalize(int digits_length, int tk) | |
153 | { | |
154 | char *token = malloc((size_t)digits_length + 1); | |
155 | if (token == NULL) { | |
156 | fprintf (stderr, "Error during memory allocation\n"); | |
157 | return token; | |
158 | } else { | |
159 | int extra_char = digits_length < 10 ? 0 : 1; | |
160 | char *fmt = calloc(1, 5 + extra_char); | |
161 | if (fmt == NULL) { | |
162 | fprintf (stderr, "Error during memory allocation\n"); | |
163 | free (token); | |
164 | return fmt; | |
165 | } | |
166 | memcpy (fmt, "%.", 3); | |
167 | snprintf (fmt + 2, 2 + extra_char, "%d", digits_length); | |
168 | memcpy (fmt + 3 + extra_char, "d", 2); | |
169 | snprintf (token, digits_length + 1, fmt, tk); | |
170 | free (fmt); | |
171 | } | |
172 | return token; | |
173 | } | |
174 | ||
175 | ||
176 | static int | |
177 | check_period(int period) | |
178 | { | |
179 | if (period <= 0 || period > 120) { | |
180 | return INVALID_PERIOD; | |
181 | } | |
182 | return VALID; | |
183 | } | |
184 | ||
185 | ||
186 | static int | |
187 | check_otp_len(int digits_length) | |
188 | { | |
189 | if (digits_length < 3 || digits_length > 10) { | |
190 | return INVALID_DIGITS; | |
191 | } | |
192 | return VALID; | |
193 | } | |
194 | ||
195 | ||
196 | static int | |
197 | check_algo(int algo) | |
198 | { | |
199 | if (algo != SHA1 && algo != SHA256 && algo != SHA512) { | |
200 | return INVALID_ALGO; | |
201 | } else { | |
202 | return VALID; | |
203 | } | |
204 | } | |
205 | ||
206 | ||
207 | char * | |
208 | get_hotp(const char *secret, long timestamp, int digits, int algo, cotp_error_t *err_code) | |
209 | { | |
210 | if (check_gcrypt() == -1) { | |
39 | 211 | *err_code = GCRYPT_VERSION_MISMATCH; |
40 | 212 | return NULL; |
41 | 213 | } |
42 | 214 | |
43 | if (check_algo (algo) == INVALID_ALGO) { | |
215 | if (check_algo(algo) == INVALID_ALGO) { | |
44 | 216 | *err_code = INVALID_ALGO; |
45 | 217 | return NULL; |
46 | 218 | } |
47 | 219 | |
48 | if (check_otp_len (digits) == INVALID_DIGITS) { | |
220 | if (check_otp_len(digits) == INVALID_DIGITS) { | |
49 | 221 | *err_code = INVALID_DIGITS; |
50 | 222 | return NULL; |
51 | 223 | } |
52 | 224 | |
53 | if (counter < 0) { | |
54 | *err_code = INVALID_COUNTER; | |
55 | return NULL; | |
56 | } | |
57 | ||
58 | unsigned char *hmac = compute_hmac (secret, counter, algo); | |
225 | unsigned char *hmac = compute_hmac(secret, timestamp, algo); | |
59 | 226 | if (hmac == NULL) { |
60 | 227 | *err_code = INVALID_B32_INPUT; |
61 | 228 | return NULL; |
62 | 229 | } |
63 | 230 | |
64 | int tk = truncate (hmac, digits, algo); | |
65 | free (hmac); | |
66 | ||
67 | *err_code = NO_ERROR; | |
68 | ||
69 | return finalize (digits, tk); | |
70 | } | |
71 | ||
72 | ||
73 | char * | |
74 | get_totp_at (const char *secret, | |
75 | long current_timestamp, | |
76 | int digits, | |
77 | int period, | |
78 | int algo, | |
79 | cotp_error_t *err_code) | |
80 | { | |
81 | if (check_gcrypt () == -1) { | |
82 | *err_code = GCRYPT_VERSION_MISMATCH; | |
83 | return NULL; | |
84 | } | |
85 | ||
86 | if (check_otp_len (digits) == INVALID_DIGITS) { | |
87 | *err_code = INVALID_DIGITS; | |
88 | return NULL; | |
89 | } | |
90 | ||
91 | if (check_period (period) == INVALID_PERIOD) { | |
92 | *err_code = INVALID_PERIOD; | |
93 | return NULL; | |
94 | } | |
95 | ||
96 | long timestamp = current_timestamp / period; | |
97 | ||
98 | cotp_error_t err; | |
99 | char *totp = get_hotp (secret, timestamp, digits, algo, &err); | |
100 | if (err != NO_ERROR && err != VALID) { | |
101 | *err_code = err; | |
102 | return NULL; | |
103 | } | |
104 | ||
105 | return totp; | |
106 | } | |
107 | ||
108 | ||
109 | char * | |
110 | get_totp (const char *secret, | |
111 | int digits, | |
112 | int period, | |
113 | int algo, | |
114 | cotp_error_t *err_code) | |
115 | { | |
116 | return get_totp_at (secret, (long)time(NULL), digits, period, algo, err_code); | |
117 | } | |
118 | ||
119 | ||
120 | char * | |
121 | get_steam_totp (const char *secret, | |
122 | int period, | |
123 | cotp_error_t *err_code) | |
231 | int tk = truncate(hmac, digits, algo); | |
232 | char *token = finalize(digits, tk); | |
233 | ||
234 | free(hmac); | |
235 | return token; | |
236 | } | |
237 | ||
238 | ||
239 | char * | |
240 | get_totp(const char *secret, int digits, int period, int algo, cotp_error_t *err_code) | |
241 | { | |
242 | return get_totp_at(secret, (long)time(NULL), digits, period, algo, err_code); | |
243 | } | |
244 | ||
245 | ||
246 | char * | |
247 | get_steam_totp (const char *secret, int period, cotp_error_t *err_code) | |
124 | 248 | { |
125 | 249 | // AFAIK, the secret is stored base64 encoded on the device. As I don't have time to waste on reverse engineering |
126 | 250 | // this non-standard solution, the user is responsible for decoding the secret in whatever format this is and then |
130 | 254 | |
131 | 255 | |
132 | 256 | char * |
133 | get_steam_totp_at (const char *secret, | |
134 | long current_timestamp, | |
135 | int period, | |
136 | cotp_error_t *err_code) | |
137 | { | |
138 | if (check_gcrypt () == -1) { | |
257 | get_totp_at(const char *secret, long current_timestamp, int digits, int period, int algo, cotp_error_t *err_code) | |
258 | { | |
259 | if (check_gcrypt() == -1) { | |
139 | 260 | *err_code = GCRYPT_VERSION_MISMATCH; |
140 | 261 | return NULL; |
141 | 262 | } |
142 | 263 | |
143 | if (check_period (period) == INVALID_PERIOD) { | |
264 | if (check_otp_len(digits) == INVALID_DIGITS) { | |
265 | *err_code = INVALID_DIGITS; | |
266 | return NULL; | |
267 | } | |
268 | ||
269 | if (check_period(period) == INVALID_PERIOD) { | |
144 | 270 | *err_code = INVALID_PERIOD; |
145 | 271 | return NULL; |
146 | 272 | } |
147 | 273 | |
148 | 274 | long timestamp = current_timestamp / period; |
149 | 275 | |
150 | unsigned char *hmac = compute_hmac (secret, timestamp, SHA1); | |
276 | cotp_error_t err; | |
277 | char *token = get_hotp(secret, timestamp, digits, algo, &err); | |
278 | if (token == NULL) { | |
279 | *err_code = err; | |
280 | return NULL; | |
281 | } | |
282 | return token; | |
283 | } | |
284 | ||
285 | ||
286 | char * | |
287 | get_steam_totp_at (const char *secret, long current_timestamp, int period, cotp_error_t *err_code) | |
288 | { | |
289 | if (check_gcrypt() == -1) { | |
290 | *err_code = GCRYPT_VERSION_MISMATCH; | |
291 | return NULL; | |
292 | } | |
293 | ||
294 | if (check_period(period) == INVALID_PERIOD) { | |
295 | *err_code = INVALID_PERIOD; | |
296 | return NULL; | |
297 | } | |
298 | ||
299 | long timestamp = current_timestamp / period; | |
300 | ||
301 | unsigned char *hmac = compute_hmac(secret, timestamp, SHA1); | |
151 | 302 | if (hmac == NULL) { |
152 | 303 | *err_code = INVALID_B32_INPUT; |
153 | 304 | return NULL; |
154 | 305 | } |
155 | 306 | |
156 | char *totp = get_steam_code (hmac); | |
307 | char * totp = get_steam_code(hmac); | |
157 | 308 | |
158 | 309 | free(hmac); |
159 | ||
160 | 310 | return totp; |
161 | 311 | } |
162 | 312 | |
163 | 313 | |
164 | int64_t | |
165 | otp_to_int (const char *otp, | |
166 | cotp_error_t *err_code) | |
167 | { | |
168 | size_t len = strlen (otp); | |
169 | if (len < MIN_DIGTS || len > MAX_DIGITS) { | |
170 | *err_code = INVALID_USER_INPUT; | |
171 | return -1; | |
172 | } | |
173 | ||
174 | if (otp[0] == '0') { | |
175 | *err_code = MISSING_LEADING_ZERO; | |
176 | } | |
177 | ||
178 | return strtoll (otp, NULL, 10); | |
179 | } | |
180 | ||
181 | ||
182 | static int | |
183 | check_gcrypt (void) | |
184 | { | |
185 | if (!gcry_control (GCRYCTL_INITIALIZATION_FINISHED_P)) { | |
186 | if (!gcry_check_version ("1.8.0")) { | |
187 | fprintf (stderr, "libgcrypt v1.8.0 and above is required\n"); | |
188 | return -1; | |
189 | } | |
190 | gcry_control (GCRYCTL_DISABLE_SECMEM, 0); | |
191 | gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); | |
192 | } | |
193 | return 0; | |
194 | } | |
195 | ||
196 | ||
197 | static char * | |
198 | normalize_secret (const char *K) | |
199 | { | |
200 | char *nK = calloc (strlen (K) + 1, 1); | |
201 | if (nK == NULL) { | |
202 | fprintf (stderr, "Error during memory allocation\n"); | |
203 | return nK; | |
204 | } | |
205 | for (int i = 0, j = 0; K[i] != '\0'; i++) { | |
206 | if (K[i] != ' ') { | |
207 | nK[j++] = islower(K[i]) ? (char) toupper(K[i]) : K[i]; | |
208 | } | |
209 | } | |
210 | return nK; | |
211 | } | |
212 | ||
213 | ||
214 | static char * | |
215 | get_steam_code (const unsigned char *hmac) | |
216 | { | |
217 | int offset = (hmac[gcry_md_get_algo_dlen (GCRY_MD_SHA1)-1] & 0x0f); | |
218 | ||
219 | // Starting from the offset, take the successive 4 bytes while stripping the topmost bit to prevent it being handled as a signed integer | |
220 | int bin_code = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) | ((hmac[offset + 2] & 0xff) << 8) | ((hmac[offset + 3] & 0xff)); | |
221 | ||
222 | const char steam_alphabet[] = "23456789BCDFGHJKMNPQRTVWXY"; | |
223 | ||
224 | char code[6]; | |
225 | size_t steam_alphabet_len = strlen (steam_alphabet); | |
226 | for (int i = 0; i < 5; i++) { | |
227 | int mod = (int)(bin_code % steam_alphabet_len); | |
228 | bin_code = (int)(bin_code / steam_alphabet_len); | |
229 | code[i] = steam_alphabet[mod]; | |
230 | } | |
231 | code[5] = '\0'; | |
232 | ||
233 | return strdup(code); | |
234 | } | |
235 | ||
236 | ||
237 | static int | |
238 | truncate (const unsigned char *hmac, | |
239 | int digits_length, | |
240 | int algo) | |
241 | { | |
242 | // take the lower four bits of the last byte | |
243 | int offset = hmac[gcry_md_get_algo_dlen (algo) - 1] & 0x0f; | |
244 | ||
245 | // Starting from the offset, take the successive 4 bytes while stripping the topmost bit to prevent it being handled as a signed integer | |
246 | int bin_code = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) | ((hmac[offset + 2] & 0xff) << 8) | ((hmac[offset + 3] & 0xff)); | |
247 | ||
248 | long long int DIGITS_POWER[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000}; | |
249 | int token = (int)(bin_code % DIGITS_POWER[digits_length]); | |
250 | ||
251 | return token; | |
252 | } | |
253 | ||
254 | ||
255 | static unsigned char * | |
256 | compute_hmac (const char *K, | |
257 | long C, | |
258 | int algo) | |
259 | { | |
260 | size_t secret_len = (size_t)((strlen(K) + 1.6 - 1) / 1.6); | |
261 | ||
262 | char *normalized_K = normalize_secret (K); | |
263 | if (normalized_K == NULL) { | |
264 | return NULL; | |
265 | } | |
266 | ||
314 | int | |
315 | totp_verify(const char *secret, const char *user_totp, int digits, int period, int algo) | |
316 | { | |
267 | 317 | cotp_error_t err; |
268 | unsigned char *secret = base32_decode (normalized_K, strlen(normalized_K), &err); | |
269 | free (normalized_K); | |
270 | if (secret == NULL) { | |
271 | return NULL; | |
272 | } | |
273 | ||
274 | unsigned char C_reverse_byte_order[8]; | |
275 | int j, i; | |
276 | for (j = 0, i = 7; j < 8 && i >= 0; j++, i--) | |
277 | C_reverse_byte_order[i] = ((unsigned char *) &C)[j]; | |
278 | ||
279 | gcry_md_hd_t hd; | |
280 | gpg_error_t gpg_err = gcry_md_open (&hd, algo, GCRY_MD_FLAG_HMAC); | |
281 | if (gpg_err) { | |
282 | fprintf (stderr, "Error while opening the cipher handle.\n"); | |
283 | free (secret); | |
284 | return NULL; | |
285 | } | |
286 | gpg_err = gcry_md_setkey (hd, secret, secret_len); | |
287 | if (gpg_err) { | |
288 | fprintf (stderr, "Error while setting the cipher key.\n"); | |
289 | free (secret); | |
290 | gcry_md_close (hd); | |
291 | return NULL; | |
292 | } | |
293 | gcry_md_write (hd, C_reverse_byte_order, sizeof (C_reverse_byte_order)); | |
294 | gcry_md_final (hd); | |
295 | ||
296 | unsigned char *hmac_tmp = gcry_md_read (hd, algo); | |
297 | if (hmac_tmp == NULL) { | |
298 | fprintf (stderr, "Error getting digest\n"); | |
299 | free (secret); | |
300 | gcry_md_close (hd); | |
301 | return NULL; | |
302 | } | |
303 | ||
304 | size_t dlen = gcry_md_get_algo_dlen(algo); | |
305 | unsigned char *hmac = malloc (dlen); | |
306 | if (hmac == NULL) { | |
307 | fprintf (stderr, "Error allocating memory"); | |
308 | free (secret); | |
309 | gcry_md_close (hd); | |
310 | return NULL; | |
311 | } | |
312 | memcpy (hmac, hmac_tmp, dlen); | |
313 | ||
314 | free (secret); | |
315 | ||
316 | gcry_md_close (hd); | |
317 | ||
318 | return hmac; | |
319 | } | |
320 | ||
321 | ||
322 | static char * | |
323 | finalize (int digits_length, | |
324 | int tk) | |
325 | { | |
326 | char *token = calloc (digits_length + 1, 1); | |
327 | if (!token) return token; | |
328 | char fmt[6]; | |
329 | sprintf (fmt, "%%0%dd", digits_length); | |
330 | snprintf (token, digits_length + 1, fmt, tk); | |
331 | return token; | |
332 | } | |
333 | ||
334 | ||
335 | static int | |
336 | check_period (int period) | |
337 | { | |
338 | return (period <= 0 || period > 120) ? INVALID_PERIOD : VALID; | |
339 | } | |
340 | ||
341 | ||
342 | static int | |
343 | check_otp_len (int digits_length) | |
344 | { | |
345 | return (digits_length < MIN_DIGTS || digits_length > MAX_DIGITS) ? INVALID_DIGITS : VALID; | |
346 | } | |
347 | ||
348 | ||
349 | static int | |
350 | check_algo (int algo) | |
351 | { | |
352 | return (algo != SHA1 && algo != SHA256 && algo != SHA512) ? INVALID_ALGO : VALID; | |
353 | }⏎ | |
318 | char *current_totp = get_totp(secret, digits, period, algo, &err); | |
319 | if (current_totp == NULL) { | |
320 | return err; | |
321 | } | |
322 | ||
323 | int token_status; | |
324 | if (strcmp(current_totp, user_totp) != 0) { | |
325 | token_status = INVALID_OTP; | |
326 | } else { | |
327 | token_status = VALID; | |
328 | } | |
329 | free(current_totp); | |
330 | ||
331 | return token_status; | |
332 | } | |
333 | ||
334 | ||
335 | int | |
336 | hotp_verify(const char *K, long C, int N, const char *user_hotp, int algo) | |
337 | { | |
338 | cotp_error_t err; | |
339 | char *current_hotp = get_hotp(K, C, N, algo, &err); | |
340 | if (current_hotp == NULL) { | |
341 | return err; | |
342 | } | |
343 | ||
344 | int token_status; | |
345 | if (strcmp(current_hotp, user_hotp) != 0) { | |
346 | token_status = INVALID_OTP; | |
347 | } else { | |
348 | token_status = VALID; | |
349 | } | |
350 | free(current_hotp); | |
351 | ||
352 | return token_status; | |
353 | } |
0 | #include <stdint.h> | |
1 | #include <stdlib.h> | |
2 | #include <string.h> | |
3 | #include "../cotp.h" | |
4 | ||
5 | #define BITS_PER_BYTE 8 | |
6 | #define BITS_PER_B32_BLOCK 5 | |
7 | ||
8 | // 64 MB should be more than enough | |
9 | #define MAX_ENCODE_INPUT_LEN (64*1024*1024) | |
10 | // if 64 MB of data is encoded than it should be also possible to decode it. That's why a bigger input is allowed for decoding | |
11 | #define MAX_DECODE_BASE32_INPUT_LEN ((MAX_ENCODE_INPUT_LEN * 8 + 4) / 5) | |
12 | ||
13 | static int is_valid_b32_input (const char *user_data); | |
14 | ||
15 | static int get_char_index (uint8_t c); | |
16 | ||
17 | static cotp_error_t check_input (const uint8_t *user_data, | |
18 | size_t data_len, | |
19 | int max_len); | |
20 | ||
21 | static int strip_char (char *str, | |
22 | char strip); | |
23 | ||
24 | static const uint8_t b32_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; | |
25 | ||
26 | ||
27 | // The encoding process represents 40-bit groups of input bits as output strings of 8 encoded characters. The input data must be null terminated. | |
28 | char * | |
29 | base32_encode (const uint8_t *user_data, | |
30 | size_t data_len, | |
31 | cotp_error_t *err_code) | |
32 | { | |
33 | cotp_error_t error = check_input (user_data, data_len, MAX_ENCODE_INPUT_LEN); | |
34 | if (error == EMPTY_STRING) { | |
35 | return strdup (""); | |
36 | } | |
37 | if (error != NO_ERROR) { | |
38 | *err_code = error; | |
39 | return NULL; | |
40 | } | |
41 | ||
42 | size_t user_data_chars = 0, total_bits = 0; | |
43 | int num_of_equals = 0; | |
44 | for (int i = 0; i < data_len; i++) { | |
45 | // As it's not known whether data_len is with or without the +1 for the null byte, a manual check is required. | |
46 | // Check for null byte only at the end of the user given length, otherwise issue#23 may occur | |
47 | if (user_data[i] == '\0' && i == data_len-1) { | |
48 | break; | |
49 | } else { | |
50 | total_bits += 8; | |
51 | user_data_chars += 1; | |
52 | } | |
53 | } | |
54 | switch (total_bits % 40) { | |
55 | case 8: num_of_equals = 6; break; | |
56 | case 16: num_of_equals = 4; break; | |
57 | case 24: num_of_equals = 3; break; | |
58 | case 32: num_of_equals = 1; break; | |
59 | } | |
60 | ||
61 | size_t output_length = (user_data_chars * 8 + 4) / 5; | |
62 | char *encoded_data = calloc (output_length + num_of_equals + 1, 1); | |
63 | if (encoded_data == NULL) { | |
64 | *err_code = MEMORY_ALLOCATION_ERROR; | |
65 | return NULL; | |
66 | } | |
67 | ||
68 | for (int i = 0, j = 0; i < user_data_chars;) { | |
69 | uint64_t first_octet = user_data[i++]; | |
70 | uint64_t second_octet = i < user_data_chars ? user_data[i++] : 0; | |
71 | uint64_t third_octet = i < user_data_chars ? user_data[i++] : 0; | |
72 | uint64_t fourth_octet = i < user_data_chars ? user_data[i++] : 0; | |
73 | uint64_t fifth_octet = i < user_data_chars ? user_data[i++] : 0; | |
74 | uint64_t quintuple = | |
75 | ((first_octet >> 3) << 35) + | |
76 | ((((first_octet & 0x7) << 2) | (second_octet >> 6)) << 30) + | |
77 | (((second_octet & 0x3F) >> 1) << 25) + | |
78 | ((((second_octet & 0x01) << 4) | (third_octet >> 4)) << 20) + | |
79 | ((((third_octet & 0xF) << 1) | (fourth_octet >> 7)) << 15) + | |
80 | (((fourth_octet & 0x7F) >> 2) << 10) + | |
81 | ((((fourth_octet & 0x3) << 3) | (fifth_octet >> 5)) << 5) + | |
82 | (fifth_octet & 0x1F); | |
83 | ||
84 | encoded_data[j++] = (char)b32_alphabet[(quintuple >> 35) & 0x1F]; | |
85 | encoded_data[j++] = (char)b32_alphabet[(quintuple >> 30) & 0x1F]; | |
86 | encoded_data[j++] = (char)b32_alphabet[(quintuple >> 25) & 0x1F]; | |
87 | encoded_data[j++] = (char)b32_alphabet[(quintuple >> 20) & 0x1F]; | |
88 | encoded_data[j++] = (char)b32_alphabet[(quintuple >> 15) & 0x1F]; | |
89 | encoded_data[j++] = (char)b32_alphabet[(quintuple >> 10) & 0x1F]; | |
90 | encoded_data[j++] = (char)b32_alphabet[(quintuple >> 5) & 0x1F]; | |
91 | encoded_data[j++] = (char)b32_alphabet[(quintuple >> 0) & 0x1F]; | |
92 | } | |
93 | ||
94 | for (int i = 0; i < num_of_equals; i++) { | |
95 | encoded_data[output_length + i] = '='; | |
96 | } | |
97 | encoded_data[output_length + num_of_equals] = '\0'; | |
98 | ||
99 | *err_code = NO_ERROR; | |
100 | ||
101 | return encoded_data; | |
102 | } | |
103 | ||
104 | ||
105 | uint8_t * | |
106 | base32_decode (const char *user_data_untrimmed, | |
107 | size_t data_len, | |
108 | cotp_error_t *err_code) | |
109 | { | |
110 | cotp_error_t error = check_input ((uint8_t *)user_data_untrimmed, data_len, MAX_DECODE_BASE32_INPUT_LEN); | |
111 | if (error == EMPTY_STRING) { | |
112 | return (uint8_t *)strdup (""); | |
113 | } | |
114 | if (error != NO_ERROR) { | |
115 | *err_code = error; | |
116 | return NULL; | |
117 | } | |
118 | ||
119 | char *user_data = strdup (user_data_untrimmed); | |
120 | if (user_data == NULL) { | |
121 | *err_code = MEMORY_ALLOCATION_ERROR; | |
122 | return NULL; | |
123 | } | |
124 | data_len -= strip_char(user_data, ' '); | |
125 | ||
126 | if (!is_valid_b32_input(user_data)) { | |
127 | free (user_data); | |
128 | *err_code = INVALID_B32_INPUT; | |
129 | return NULL; | |
130 | } | |
131 | ||
132 | size_t user_data_chars = 0; | |
133 | for (int i = 0; i < data_len; i++) { | |
134 | // As it's not known whether data_len is with or without the +1 for the null byte, a manual check is required. | |
135 | if (user_data[i] != '=' && user_data[i] != '\0') { | |
136 | user_data_chars += 1; | |
137 | } | |
138 | } | |
139 | ||
140 | size_t output_length = (size_t)((user_data_chars + 1.6 + 1) / 1.6); // round up | |
141 | uint8_t *decoded_data = calloc(output_length + 1, 1); | |
142 | if (decoded_data == NULL) { | |
143 | free (user_data); | |
144 | *err_code = MEMORY_ALLOCATION_ERROR; | |
145 | return NULL; | |
146 | } | |
147 | ||
148 | uint8_t mask, current_byte = 0; | |
149 | int bits_left = 8; | |
150 | for (int i = 0, j = 0; i < user_data_chars; i++) { | |
151 | int char_index = get_char_index ((uint8_t)user_data[i]); | |
152 | if (bits_left > BITS_PER_B32_BLOCK) { | |
153 | mask = (uint8_t)char_index << (bits_left - BITS_PER_B32_BLOCK); | |
154 | current_byte = (uint8_t) (current_byte | mask); | |
155 | bits_left -= BITS_PER_B32_BLOCK; | |
156 | } else { | |
157 | mask = (uint8_t)char_index >> (BITS_PER_B32_BLOCK - bits_left); | |
158 | current_byte = (uint8_t) (current_byte | mask); | |
159 | decoded_data[j++] = current_byte; | |
160 | current_byte = (uint8_t) (char_index << (BITS_PER_BYTE - BITS_PER_B32_BLOCK + bits_left)); | |
161 | bits_left += BITS_PER_BYTE - BITS_PER_B32_BLOCK; | |
162 | } | |
163 | } | |
164 | decoded_data[output_length] = '\0'; | |
165 | ||
166 | free (user_data); | |
167 | ||
168 | *err_code = NO_ERROR; | |
169 | ||
170 | return decoded_data; | |
171 | } | |
172 | ||
173 | ||
174 | static int | |
175 | is_valid_b32_input (const char *user_data) | |
176 | { | |
177 | // Create a lookup table for ASCII characters | |
178 | uint8_t table[128]; | |
179 | memset (table, 0, sizeof (table)); | |
180 | for (size_t i = 0; i < sizeof (b32_alphabet); i++) { | |
181 | table[b32_alphabet[i]] = 1; | |
182 | } | |
183 | table['='] = 1; | |
184 | ||
185 | const char *p; | |
186 | for (p = user_data; *p; p++) { | |
187 | if (!table[*(uint8_t *)p]) { | |
188 | return 0; | |
189 | } | |
190 | } | |
191 | return 1; | |
192 | } | |
193 | ||
194 | ||
195 | static int | |
196 | get_char_index (uint8_t c) | |
197 | { | |
198 | for (int i = 0; i < sizeof (b32_alphabet); i++) { | |
199 | if (b32_alphabet[i] == c) { | |
200 | return i; | |
201 | } | |
202 | } | |
203 | return -1; | |
204 | } | |
205 | ||
206 | ||
207 | static int | |
208 | strip_char (char *str, | |
209 | char strip) | |
210 | { | |
211 | int found = 0; | |
212 | ||
213 | // Create a lookup table for ASCII characters | |
214 | uint8_t table[128]; | |
215 | memset (table, 0, sizeof (table)); | |
216 | table[(int)strip] = 1; | |
217 | ||
218 | // Iterate through the string using pointers | |
219 | char *p, *q; | |
220 | for (q = p = str; *p; p++) { | |
221 | if (!table[*(uint8_t *)p]) { | |
222 | *q++ = *p; | |
223 | } else { | |
224 | found++; | |
225 | } | |
226 | } | |
227 | *q = '\0'; | |
228 | return found; | |
229 | } | |
230 | ||
231 | ||
232 | static cotp_error_t | |
233 | check_input (const uint8_t *user_data, | |
234 | size_t data_len, | |
235 | int max_len) | |
236 | { | |
237 | if (!user_data || (data_len == 0 && user_data[0] != '\0') || (data_len > (size_t)max_len)) { | |
238 | return INVALID_USER_INPUT; | |
239 | } | |
240 | ||
241 | if (user_data[0] == '\0') { | |
242 | return EMPTY_STRING; | |
243 | } | |
244 | ||
245 | return NO_ERROR; | |
246 | }⏎ |
0 | 0 | |
1 | 1 | IF(BUILD_TESTING) |
2 | 2 | add_executable (test_cotp test_otp.c) |
3 | add_executable (test_base32encode test_base32encode.c) | |
4 | add_executable (test_base32decode test_base32decode.c) | |
5 | 3 | |
6 | target_link_libraries (test_cotp -lcotp -lcriterion -lgcrypt) | |
7 | target_link_libraries (test_base32encode -lcotp -lcriterion -lgcrypt) | |
8 | target_link_libraries (test_base32decode -lcotp -lcriterion -lgcrypt) | |
4 | target_link_libraries (test_cotp -lcotp -lcriterion -lbaseencode -lgcrypt) | |
9 | 5 | target_link_directories (test_cotp PRIVATE ${PROJECT_BINARY_DIR}) |
10 | target_link_directories (test_base32encode PRIVATE ${PROJECT_BINARY_DIR}) | |
11 | target_link_directories (test_base32decode PRIVATE ${PROJECT_BINARY_DIR}) | |
12 | 6 | add_dependencies (test_cotp cotp) |
13 | add_dependencies (test_base32encode cotp) | |
14 | add_dependencies (test_base32decode cotp) | |
15 | 7 | |
16 | 8 | add_test (NAME TestCOTP COMMAND test_cotp) |
17 | add_test (NAME TestBase32Encode COMMAND test_base32encode) | |
18 | add_test (NAME TestBase32Decode COMMAND test_base32decode) | |
19 | 9 | ENDIF(BUILD_TESTING) |
0 | #include <stdio.h> | |
1 | #include <criterion/criterion.h> | |
2 | #include "../src/cotp.h" | |
3 | ||
4 | ||
5 | Test(b32_decode_test, b32_all_chars) { | |
6 | cotp_error_t err; | |
7 | const char *k = "IFCEMRZUGEZSDQVDEQSSMJRIFAXT6XWDU7B2SKS3LURSSLJOFR6DYPRL"; | |
8 | const char *k_dec = "ADFG413!£$%&&((/?^çé*[]#)-.,|<>+"; | |
9 | ||
10 | char *dk = base32_decode (k, strlen(k)+1, &err); | |
11 | ||
12 | cr_expect(strcmp(dk, k_dec) == 0, "Expected %s to be equal to %s", dk, k_dec); | |
13 | ||
14 | free(dk); | |
15 | } | |
16 | ||
17 | ||
18 | Test(b32_decode_test, b32_all_chars_noplusone) { | |
19 | cotp_error_t err; | |
20 | const char *k = "IFCEMRZUGEZSDQVDEQSSMJRIFAXT6XWDU7B2SKS3LURSSLJOFR6DYPRL"; | |
21 | const char *k_dec = "ADFG413!£$%&&((/?^çé*[]#)-.,|<>+"; | |
22 | ||
23 | char *dk = base32_decode (k, strlen(k), &err); | |
24 | ||
25 | cr_expect(strcmp(dk, k_dec) == 0, "Expected %s to be equal to %s", dk, k_dec); | |
26 | ||
27 | free(dk); | |
28 | } | |
29 | ||
30 | ||
31 | Test(b32_decode_test, b32_rfc4648) { | |
32 | cotp_error_t err; | |
33 | const char *k[] = {"", "MY======", "MZXQ====", "MZXW6===", "MZXW6YQ=", "MZXW6YTB", "MZXW6YTBOI======"}; | |
34 | const char *k_dec[] = {"", "f", "fo", "foo", "foob", "fooba", "foobar"}; | |
35 | ||
36 | for (int i = 0; i < 7; i++) { | |
37 | char *dk = base32_decode (k[i], strlen(k[i])+1, &err); | |
38 | cr_expect(strcmp(dk, k_dec[i]) == 0, "Expected %s to be equal to %s", dk, k_dec[i]); | |
39 | free(dk); | |
40 | } | |
41 | } | |
42 | ||
43 | ||
44 | Test(b32_decode_test, b32_rfc4648_noplusone) { | |
45 | cotp_error_t err; | |
46 | const char *k[] = {"", "MY======", "MZXQ====", "MZXW6===", "MZXW6YQ=", "MZXW6YTB", "MZXW6YTBOI======"}; | |
47 | const char *k_dec[] = {"", "f", "fo", "foo", "foob", "fooba", "foobar"}; | |
48 | ||
49 | for (int i = 0; i < 7; i++) { | |
50 | char *dk = base32_decode (k[i], strlen(k[i]), &err); | |
51 | cr_expect(strcmp(dk, k_dec[i]) == 0, "Expected %s to be equal to %s", dk, k_dec[i]); | |
52 | free(dk); | |
53 | } | |
54 | } | |
55 | ||
56 | ||
57 | Test(b32_decode_test, b32_invalid_input) { | |
58 | cotp_error_t err; | |
59 | const char *k = "£&/(&/"; | |
60 | size_t len = strlen(k); | |
61 | ||
62 | uint8_t *dk = base32_decode (k, len, &err); | |
63 | ||
64 | cr_expect_null (dk, "%s"); | |
65 | cr_expect_eq (err, INVALID_B32_INPUT); | |
66 | } | |
67 | ||
68 | ||
69 | Test(b32_decode_test, b32_decode_input_exceeded) { | |
70 | cotp_error_t err; | |
71 | const char *k = "ASDF"; | |
72 | size_t len = 128*1024*1024; | |
73 | ||
74 | uint8_t *dk = base32_decode (k, len, &err); | |
75 | ||
76 | cr_expect_null (dk, "%s"); | |
77 | cr_expect_eq (err, INVALID_USER_INPUT); | |
78 | } | |
79 | ||
80 | ||
81 | Test(b32_decode_test, b32_decode_input_whitespaces) { | |
82 | cotp_error_t err; | |
83 | const char *k = "MZ XW 6Y TB"; | |
84 | const char *expected = "fooba"; | |
85 | ||
86 | uint8_t *dk = base32_decode (k, strlen(k), &err); | |
87 | ||
88 | cr_expect_str_eq (dk, expected, "%s"); | |
89 | } | |
90 | ||
91 | Test(b32_decode_test, b32_decode_encode_null) { | |
92 | const char* token = "LLFTSZYMUGKHEDQBAAACAZAMUFKKVFLS"; | |
93 | cotp_error_t err; | |
94 | ||
95 | uint8_t* binary = base32_decode (token, strlen(token)+1, &err); | |
96 | cr_expect_eq (err, NO_ERROR); | |
97 | ||
98 | char* result = base32_encode (binary, 20, &err); | |
99 | cr_expect_eq (err, NO_ERROR); | |
100 | ||
101 | cr_expect_str_eq (result, token, "%s"); | |
102 | } | |
103 |
0 | #include <stdio.h> | |
1 | #include <criterion/criterion.h> | |
2 | #include "../src/cotp.h" | |
3 | ||
4 | ||
5 | Test(b32_encode_test, null_input) { | |
6 | cotp_error_t err; | |
7 | const char *k = NULL; | |
8 | ||
9 | char *ek = base32_encode (k, 5, &err); | |
10 | ||
11 | cr_expect_null (ek, "%s"); | |
12 | } | |
13 | ||
14 | ||
15 | Test(b32_encode_test, data_nodata_size_nosize) { | |
16 | cotp_error_t err; | |
17 | const char *k1 = ""; | |
18 | const char *k2 = "asdiasjdijis"; | |
19 | ||
20 | // test no-data with given size, data with no-size and no-data no-size | |
21 | char *ek1 = base32_encode (k1, 30, &err); | |
22 | char *ek2 = base32_encode (k2, 0, &err); | |
23 | ||
24 | cr_expect (strcmp (k1, ek1) == 0, "Expected %s to be equal to %s", ek1, k1); | |
25 | cr_expect_null (ek2, "%s"); | |
26 | ||
27 | free (ek1); | |
28 | } | |
29 | ||
30 | ||
31 | Test(b32_encode_test, b32_all_chars) { | |
32 | cotp_error_t err; | |
33 | const char *k = "ADFG413!£$%&&((/?^çé*[]#)-.,|<>+"; | |
34 | const char *k_enc = "IFCEMRZUGEZSDQVDEQSSMJRIFAXT6XWDU7B2SKS3LURSSLJOFR6DYPRL"; | |
35 | ||
36 | char *ek = base32_encode (k, strlen(k)+1, &err); | |
37 | ||
38 | cr_expect (strcmp (ek, k_enc) == 0, "Expected %s to be equal to %s", ek, k_enc); | |
39 | ||
40 | free (ek); | |
41 | } | |
42 | ||
43 | ||
44 | Test(b32_encode_test, b32_all_chars_noplusone) { | |
45 | cotp_error_t err; | |
46 | const char *k = "ADFG413!£$%&&((/?^çé*[]#)-.,|<>+"; | |
47 | const char *k_enc = "IFCEMRZUGEZSDQVDEQSSMJRIFAXT6XWDU7B2SKS3LURSSLJOFR6DYPRL"; | |
48 | ||
49 | char *ek = base32_encode (k, strlen(k), &err); | |
50 | ||
51 | cr_expect (strcmp (ek, k_enc) == 0, "Expected %s to be equal to %s", ek, k_enc); | |
52 | ||
53 | free (ek); | |
54 | } | |
55 | ||
56 | ||
57 | Test(b32_encode_test, b32_rfc4648) { | |
58 | cotp_error_t err; | |
59 | const char *k[] = {"", "f", "fo", "foo", "foob", "fooba", "foobar"}; | |
60 | const char *k_enc[] = {"", "MY======", "MZXQ====", "MZXW6===", "MZXW6YQ=", "MZXW6YTB", "MZXW6YTBOI======"}; | |
61 | ||
62 | for (int i = 0; i < 7; i++) { | |
63 | char *ek = base32_encode (k[i], strlen(k[i])+1, &err); | |
64 | cr_expect (strcmp (ek, k_enc[i]) == 0, "Expected %s to be equal to %s", ek, k_enc[i]); | |
65 | free (ek); | |
66 | } | |
67 | } | |
68 | ||
69 | ||
70 | Test(b32_encode_test, b32_rfc4648_noplusone) { | |
71 | cotp_error_t err; | |
72 | const char *k[] = {"", "f", "fo", "foo", "foob", "fooba", "foobar"}; | |
73 | const char *k_enc[] = {"", "MY======", "MZXQ====", "MZXW6===", "MZXW6YQ=", "MZXW6YTB", "MZXW6YTBOI======"}; | |
74 | ||
75 | for (int i = 0; i < 7; i++) { | |
76 | char *ek = base32_encode (k[i], strlen(k[i]), &err); | |
77 | cr_expect (strcmp (ek, k_enc[i]) == 0, "Expected %s to be equal to %s", ek, k_enc[i]); | |
78 | free (ek); | |
79 | } | |
80 | } | |
81 | ||
82 | ||
83 | Test(b32_encode_test, b32_encode_input_exceeded) { | |
84 | cotp_error_t err; | |
85 | const char *k = "test"; | |
86 | size_t len = 65*1024*1024; | |
87 | ||
88 | char *ek = base32_encode (k, len, &err); | |
89 | cr_expect_null (ek, "%s"); | |
90 | cr_expect_eq (err, INVALID_USER_INPUT); | |
91 | }⏎ |
0 | 0 | #include <criterion/criterion.h> |
1 | 1 | #include <string.h> |
2 | 2 | #include "../src/cotp.h" |
3 | #include <baseencode.h> | |
4 | ||
3 | 5 | |
4 | 6 | Test(totp_rfc6238, test_8_digits_sha1) { |
5 | 7 | const char *K = "12345678901234567890"; |
6 | 8 | const int64_t counter[] = {59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000}; |
7 | 9 | const char *expected_totp[] = {"94287082", "07081804", "14050471", "89005924", "69279037", "65353130"}; |
8 | 10 | |
9 | cotp_error_t cotp_err; | |
10 | char *K_base32 = base32_encode ((const uchar *)K, strlen(K)+1, &cotp_err); | |
11 | ||
12 | cotp_error_t err; | |
13 | char *totp; | |
11 | baseencode_error_t base_err; | |
12 | char *K_base32 = base32_encode(K, strlen(K)+1, &base_err); | |
13 | ||
14 | cotp_error_t err; | |
14 | 15 | for (int i = 0; i < 6; i++) { |
15 | totp = get_totp_at (K_base32, counter[i], 8, 30, SHA1, &err); | |
16 | cr_expect_str_eq (totp, expected_totp[i], "Expected %s to be equal to %s\n", totp, expected_totp[i]); | |
17 | free (totp); | |
18 | } | |
19 | free (K_base32); | |
20 | } | |
21 | ||
22 | ||
23 | Test(totp_rfc6238, test_8_digits_sha1_toint) { | |
24 | const char *K = "12345678901234567890"; | |
25 | const int64_t counter[] = {59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000}; | |
26 | const int64_t expected_totp[] = {94287082, 7081804, 14050471, 89005924, 69279037, 65353130}; | |
27 | ||
28 | cotp_error_t cotp_err; | |
29 | char *K_base32 = base32_encode ((const uchar *)K, strlen(K)+1, &cotp_err); | |
30 | ||
31 | cotp_error_t err; | |
32 | for (int i = 0; i < 6; i++) { | |
33 | int64_t totp = otp_to_int (get_totp_at (K_base32, counter[i], 8, 30, SHA1, &err), &err); | |
34 | cr_expect_eq (totp, expected_totp[i], "Expected %08ld to be equal to %08ld\n", totp, expected_totp[i]); | |
35 | } | |
36 | free (K_base32); | |
16 | char *totp = get_totp_at(K_base32, counter[i], 8, 30, SHA1, &err); | |
17 | cr_expect_str_eq(totp, expected_totp[i], "Expected %s to be equal to %s\n", totp, expected_totp[i]); | |
18 | free(totp); | |
19 | } | |
20 | free(K_base32); | |
37 | 21 | } |
38 | 22 | |
39 | 23 | |
42 | 26 | const long counter = 1234567890; |
43 | 27 | const char *expected_totp = "0689005924"; |
44 | 28 | |
45 | cotp_error_t cotp_err; | |
46 | char *K_base32 = base32_encode ((const uchar *)K, strlen(K)+1, &cotp_err); | |
47 | ||
48 | cotp_error_t err; | |
49 | char *totp = get_totp_at (K_base32, counter, 10, 30, SHA1, &err); | |
50 | cr_expect_str_eq (totp, expected_totp, "Expected %s to be equal to %s\n", totp, expected_totp); | |
51 | free (totp); | |
52 | free (K_base32); | |
53 | } | |
54 | ||
55 | ||
56 | Test(totp_rfc6238, test_10_digits_sha1_toint) { | |
57 | const char *K = "12345678901234567890"; | |
58 | const long counter = 1234567890; | |
59 | int64_t expected_totp = 689005924; | |
60 | ||
61 | cotp_error_t cotp_err; | |
62 | char *K_base32 = base32_encode ((const uchar *)K, strlen(K)+1, &cotp_err); | |
63 | ||
64 | cotp_error_t err; | |
65 | int64_t totp = otp_to_int (get_totp_at (K_base32, counter, 10, 30, SHA1, &err), &err); | |
66 | cr_expect_eq (totp, expected_totp, "Expected %010ld to be equal to %010ld\n", totp, expected_totp); | |
67 | ||
68 | free (K_base32); | |
69 | } | |
29 | baseencode_error_t base_err; | |
30 | char *K_base32 = base32_encode(K, strlen(K)+1, &base_err); | |
31 | ||
32 | cotp_error_t err; | |
33 | char *totp = get_totp_at(K_base32, counter, 10, 30, SHA1, &err); | |
34 | cr_expect_str_eq(totp, expected_totp, "Expected %s to be equal to %s\n", totp, expected_totp); | |
35 | free(totp); | |
36 | free(K_base32); | |
37 | } | |
38 | ||
70 | 39 | |
71 | 40 | |
72 | 41 | Test(totp_rfc6238, test_8_digits_sha256) { |
74 | 43 | const int64_t counter[] = {59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000}; |
75 | 44 | const char *expected_totp[] = {"46119246", "68084774", "67062674", "91819424", "90698825", "77737706"}; |
76 | 45 | |
77 | cotp_error_t cotp_err; | |
78 | char *K_base32 = base32_encode ((const uchar *)K, strlen(K)+1, &cotp_err); | |
79 | ||
80 | cotp_error_t err; | |
81 | char *totp; | |
46 | baseencode_error_t base_err; | |
47 | char *K_base32 = base32_encode(K, strlen(K)+1, &base_err); | |
48 | ||
49 | cotp_error_t err; | |
82 | 50 | for (int i = 0; i < 6; i++) { |
83 | totp = get_totp_at (K_base32, counter[i], 8, 30, SHA256, &err); | |
84 | cr_expect_str_eq (totp, expected_totp[i], "Expected %s to be equal to %s\n", totp, expected_totp[i]); | |
85 | free (totp); | |
86 | } | |
87 | free (K_base32); | |
51 | char *totp = get_totp_at(K_base32, counter[i], 8, 30, SHA256, &err); | |
52 | cr_expect_str_eq(totp, expected_totp[i], "Expected %s to be equal to %s\n", totp, expected_totp[i]); | |
53 | free(totp); | |
54 | } | |
55 | free(K_base32); | |
88 | 56 | } |
89 | 57 | |
90 | 58 | |
93 | 61 | const int64_t counter[] = {59, 1111111109, 1111111111, 1234567890, 2000000000, 20000000000}; |
94 | 62 | const char *expected_totp[] = {"90693936", "25091201", "99943326", "93441116", "38618901", "47863826"}; |
95 | 63 | |
96 | cotp_error_t cotp_err; | |
97 | char *K_base32 = base32_encode ((const uchar *)K, strlen (K) + 1, &cotp_err); | |
98 | ||
99 | cotp_error_t err; | |
100 | char *totp; | |
64 | baseencode_error_t base_err; | |
65 | char *K_base32 = base32_encode(K, strlen(K)+1, &base_err); | |
66 | ||
67 | cotp_error_t err; | |
101 | 68 | for (int i = 0; i < 6; i++) { |
102 | totp = get_totp_at (K_base32, counter[i], 8, 30, SHA512, &err); | |
103 | cr_expect_str_eq (totp, expected_totp[i], "Expected %s to be equal to %s\n", totp, expected_totp[i]); | |
104 | free (totp); | |
105 | } | |
106 | free (K_base32); | |
69 | char *totp = get_totp_at(K_base32, counter[i], 8, 30, SHA512, &err); | |
70 | cr_expect_str_eq(totp, expected_totp[i], "Expected %s to be equal to %s\n", totp, expected_totp[i]); | |
71 | free(totp); | |
72 | } | |
73 | free(K_base32); | |
107 | 74 | } |
108 | 75 | |
109 | 76 | |
112 | 79 | const int counter[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
113 | 80 | const char *expected_hotp[] = {"755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", "399871", "520489"}; |
114 | 81 | |
115 | cotp_error_t cotp_err; | |
116 | char *K_base32 = base32_encode((const uchar *)K, strlen(K)+1, &cotp_err); | |
117 | ||
118 | cotp_error_t err; | |
119 | char *hotp; | |
82 | baseencode_error_t base_err; | |
83 | char *K_base32 = base32_encode(K, strlen(K)+1, &base_err); | |
84 | ||
85 | cotp_error_t err; | |
120 | 86 | for (int i = 0; i < 10; i++) { |
121 | hotp = get_hotp (K_base32, counter[i], 6, SHA1, &err); | |
122 | cr_expect_str_eq (hotp, expected_hotp[i], "Expected %s to be equal to %s\n", hotp, expected_hotp[i]); | |
123 | free (hotp); | |
124 | } | |
125 | free (K_base32); | |
87 | char *hotp = get_hotp(K_base32, counter[i], 6, SHA1, &err); | |
88 | cr_expect_str_eq(hotp, expected_hotp[i], "Expected %s to be equal to %s\n", hotp, expected_hotp[i]); | |
89 | free(hotp); | |
90 | } | |
91 | free(K_base32); | |
126 | 92 | } |
127 | 93 | |
128 | 94 | |
132 | 98 | cotp_error_t err; |
133 | 99 | char *totp = get_totp (K, 2, 30, SHA1, &err); |
134 | 100 | |
101 | cr_expect_null (totp, "Expected totp to be null"); | |
135 | 102 | cr_expect_eq (err, INVALID_DIGITS, "Expected %d to be equal to %d\n", err, INVALID_DIGITS); |
136 | cr_assert_null (totp); | |
137 | 103 | |
138 | 104 | free (totp); |
139 | 105 | } |
145 | 111 | cotp_error_t err; |
146 | 112 | char *totp = get_totp (K, 16, 30, SHA1, &err); |
147 | 113 | |
114 | cr_expect_null (totp, "Expected totp to be null"); | |
148 | 115 | cr_expect_eq (err, INVALID_DIGITS, "Expected %d to be equal to %d\n", err, INVALID_DIGITS); |
149 | cr_assert_null (totp); | |
150 | 116 | |
151 | 117 | free (totp); |
152 | 118 | } |
158 | 124 | cotp_error_t err; |
159 | 125 | char *totp = get_totp (K, 6, 0, SHA1, &err); |
160 | 126 | |
127 | cr_expect_null (totp, "Expected totp to be null"); | |
161 | 128 | cr_expect_eq (err, INVALID_PERIOD, "Expected %d to be equal to %d\n", err, INVALID_PERIOD); |
162 | cr_assert_null (totp); | |
163 | ||
164 | free (totp); | |
165 | } | |
166 | ||
167 | ||
168 | Test(hotp_rfc, test_totp_wrong_negative) { | |
129 | ||
130 | free (totp); | |
131 | } | |
132 | ||
133 | ||
134 | Test(hotp_rfc, test_wrong_negative) { | |
169 | 135 | const char *K = "this is a secret"; |
170 | 136 | |
171 | 137 | cotp_error_t err; |
172 | 138 | char *totp = get_totp (K, 6, -20, SHA1, &err); |
173 | 139 | |
140 | cr_expect_null (totp, "Expected totp to be null"); | |
174 | 141 | cr_expect_eq (err, INVALID_PERIOD, "Expected %d to be equal to %d\n", err, INVALID_PERIOD); |
175 | cr_assert_null (totp); | |
176 | ||
177 | free (totp); | |
178 | } | |
179 | ||
180 | ||
181 | Test(hotp_rfc, test_hotp_wrong_negative) { | |
182 | const char *K = "this is a secret"; | |
183 | ||
184 | cotp_error_t err; | |
185 | char *hotp = get_hotp (K, -6, 8, SHA1, &err); | |
186 | ||
187 | cr_expect_eq (err, INVALID_COUNTER, "Expected %d to be equal to %d\n", err, INVALID_COUNTER); | |
188 | cr_assert_null (hotp); | |
142 | ||
143 | free (totp); | |
189 | 144 | } |
190 | 145 | |
191 | 146 | |
207 | 162 | cotp_error_t err; |
208 | 163 | char *totp = get_totp (K, 6, 30, SHA1, &err); |
209 | 164 | |
165 | cr_expect_null (totp, "Expected totp to be null"); | |
210 | 166 | cr_expect_eq (err, INVALID_B32_INPUT, "Expected %d to be equal to %d\n", err, INVALID_B32_INPUT); |
211 | cr_assert_null (totp); | |
212 | 167 | } |
213 | 168 | |
214 | 169 | |
218 | 173 | cotp_error_t err; |
219 | 174 | char *totp = get_totp (K, 6, 30, GCRY_MD_MD5, &err); |
220 | 175 | |
176 | cr_expect_null (totp, "Expected totp to be null"); | |
221 | 177 | cr_expect_eq (err, INVALID_ALGO, "Expected %d to be equal to %d\n", err, INVALID_ALGO); |
222 | cr_assert_null (totp); | |
223 | 178 | } |
224 | 179 | |
225 | 180 | |
243 | 198 | char *totp = get_steam_totp (b64_encoded_secret, 30, &err); |
244 | 199 | cr_expect_null (totp, "Expected totp to be null"); |
245 | 200 | cr_expect_eq (err, INVALID_B32_INPUT, "Expected %d to be equal to %d\n", err, INVALID_B32_INPUT); |
246 | cr_assert_null (totp); | |
247 | } | |
248 | ||
201 | } | |
249 | 202 | |
250 | 203 | Test(totp_rfc6238, test_60seconds) { |
251 | const char *K = "12345678901234567890"; | |
204 | const char *secret = "12345678901234567890"; | |
252 | 205 | const char *expected_totp = "360094"; |
253 | 206 | |
254 | cotp_error_t cotp_err; | |
255 | char *secret_base32 = base32_encode ((const uchar *)K, strlen (K)+1, &cotp_err); | |
256 | ||
257 | cotp_error_t err; | |
258 | char *totp = get_totp_at (secret_base32, 1111111109, 6, 60, SHA1, &err); | |
259 | cr_expect_str_eq (totp, expected_totp, "Expected %s to be equal to %s\n", totp, expected_totp); | |
260 | ||
261 | free (totp); | |
262 | free (secret_base32); | |
263 | } | |
264 | ||
265 | ||
266 | Test(totp_int, test_err_is_missing_zero) { | |
267 | const char *K = "12345678901234567890"; | |
268 | const long counter = 1234567890; | |
269 | int64_t expected_totp = 689005924; | |
270 | ||
271 | cotp_error_t cotp_err; | |
272 | char *K_base32 = base32_encode ((const uchar *)K, strlen(K)+1, &cotp_err); | |
273 | ||
274 | cotp_error_t err; | |
275 | int64_t totp = otp_to_int (get_totp_at (K_base32, counter, 10, 30, SHA1, &err), &err); | |
276 | cr_expect_eq (err, MISSING_LEADING_ZERO, "Expected %d to be equal to %d\n", err, MISSING_LEADING_ZERO); | |
277 | ||
278 | free (K_base32); | |
279 | } | |
280 | ||
281 | ||
282 | Test(totp_int, test_err_invalid_input) { | |
283 | const char *K = "12345678901234567890"; | |
284 | ||
285 | cotp_error_t cotp_err; | |
286 | char *K_base32 = base32_encode ((const uchar *)K, strlen(K)+1, &cotp_err); | |
287 | ||
288 | cotp_error_t err; | |
289 | int64_t totp = otp_to_int ("124", &err); | |
290 | cr_expect_eq (err, INVALID_USER_INPUT, "Expected %d to be equal to %d\n", err, INVALID_USER_INPUT); | |
291 | cr_expect_eq (totp, -1, "Expected %ld to be equal to %d\n", totp, -1); | |
292 | ||
293 | free (K_base32); | |
294 | }⏎ | |
207 | baseencode_error_t base_err; | |
208 | char *secret_base32 = base32_encode(secret, strlen(secret)+1, &base_err); | |
209 | ||
210 | cotp_error_t err; | |
211 | char *totp = get_totp_at(secret_base32, 1111111109, 6, 60, SHA1, &err); | |
212 | cr_expect_str_eq(totp, expected_totp, "Expected %s to be equal to %s\n", totp, expected_totp); | |
213 | ||
214 | free(totp); | |
215 | free(secret_base32); | |
216 | } |