Created
October 3, 2024 14:30
-
-
Save pccr10001/27ccd522d6fd03c2530e18a675ad94e9 to your computer and use it in GitHub Desktop.
OpenVPN with TOTP as password supporting, which can support VPN server like Pritunl with auto-reconnecting.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This should work with all versions of OpenVPN. | |
# Use TOTP:{Base32 Key} as password for OpenVPN, e.g. TOTP:JBSWY3DPEHPK3PXP | |
# | |
# For OpenWRT | |
# Put this file to openwrt-x.x.x/package/feeds/packages/openvpn/patches/230-add-totp-as-password.patch | |
# Then build the ipk with make package/openvpn/compile V=s | |
# | |
--- a/src/openvpn/misc.c 2022-10-28 14:44:17.000000000 +0800 | |
+++ b/src/openvpn/misc.c 2024-10-03 19:01:17.322280538 +0800 | |
@@ -149,6 +149,140 @@ | |
#endif /* ifdef ENABLE_MANAGEMENT */ | |
/* | |
+ * Function to calculate the number of digits | |
+ */ | |
+int power_of_10(int n) { | |
+ int result = 1; | |
+ for (int i = 0; i < n; i++) { | |
+ result *= 10; | |
+ } | |
+ return result; | |
+} | |
+ | |
+/* | |
+ * Function to decode Base32 string to binary | |
+ */ | |
+int base32_get_decoded_length(uint8_t* in) { | |
+ size_t input_length = strnlen(in, USER_PASS_LEN); | |
+ return (input_length * 5) / 8; | |
+} | |
+ | |
+ | |
+/* | |
+ * Base32 decoder | |
+ * Ref: | |
+ * - https://github.com/google/google-authenticator-libpam/blob/master/src/base32.c | |
+ * - https://github.com/fmount/c_otp/blob/master/lib/utils.c | |
+ */ | |
+static const int8_t base32_vals[256] = { | |
+ // This map cheats and interprets: | |
+ // - the numeral zero as the letter "O" as in oscar | |
+ // - the numeral one as the letter "L" as in lima | |
+ // - the numeral eight as the letter "B" as in bravo | |
+ // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x20 | |
+ 14, 11, 26, 27, 28, 29, 30, 31, 1, -1, -1, -1, -1, 0, -1, -1, // 0x30 | |
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 0x40 | |
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 0x50 | |
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 0x60 | |
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, -1, -1, -1, -1, // 0x70 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x80 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x90 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xA0 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xB0 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xC0 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xD0 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xE0 | |
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0xF0 | |
+}; | |
+ | |
+ | |
+int | |
+validate_base32_key(char *k, size_t len) | |
+{ | |
+ | |
+ for (size_t pos = 0; (pos < len); pos++) { | |
+ if (base32_vals[k[pos]] == -1) | |
+ return 1; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+ | |
+int base32_decode(const uint8_t *encoded, uint8_t *result, int bufSize) { | |
+ unsigned int buffer = 0; | |
+ int bitsLeft = 0; | |
+ int count = 0; | |
+ for (const uint8_t *ptr = encoded; count < bufSize && *ptr; ++ptr) { | |
+ uint8_t ch = *ptr; | |
+ if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '-') { | |
+ continue; | |
+ } | |
+ buffer <<= 5; | |
+ | |
+ // Deal with commonly mistyped characters | |
+ if (ch == '0') { | |
+ ch = 'O'; | |
+ } else if (ch == '1') { | |
+ ch = 'L'; | |
+ } else if (ch == '8') { | |
+ ch = 'B'; | |
+ } | |
+ | |
+ // Look up one base32 digit | |
+ if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { | |
+ ch = (ch & 0x1F) - 1; | |
+ } else if (ch >= '2' && ch <= '7') { | |
+ ch -= '2' - 26; | |
+ } else { | |
+ return -1; | |
+ } | |
+ | |
+ buffer |= ch; | |
+ bitsLeft += 5; | |
+ if (bitsLeft >= 8) { | |
+ result[count++] = buffer >> (bitsLeft - 8); | |
+ bitsLeft -= 8; | |
+ } | |
+ } | |
+ if (count < bufSize) { | |
+ result[count] = '\000'; | |
+ } | |
+ return count; | |
+} | |
+ | |
+/* | |
+ * Get TOTP from key | |
+ */ | |
+uint32_t calculate_totp(uint8_t* key) { | |
+ time_t current_time = time(NULL); | |
+ uint64_t time_counter = current_time / 30; | |
+ uint8_t decoded_key[base32_get_decoded_length(key)]; | |
+ int key_length = base32_decode(key, decoded_key, USER_PASS_LEN); | |
+ uint8_t time_bytes[8]; | |
+ for (int i = 7; i >= 0; i--) { | |
+ time_bytes[i] = time_counter & 0xFF; | |
+ time_counter >>= 8; | |
+ } | |
+ | |
+ uint8_t hmac_result[EVP_MAX_MD_SIZE]; | |
+ unsigned int hmac_len; | |
+ | |
+ // HMAC-SHA1 using OpenSSL | |
+ HMAC(EVP_sha1(), decoded_key, key_length, time_bytes, 8, hmac_result, &hmac_len); | |
+ | |
+ int offset = hmac_result[hmac_len - 1] & 0x0F; | |
+ uint32_t binary_code = (hmac_result[offset] & 0x7F) << 24 | |
+ | (hmac_result[offset + 1] & 0xFF) << 16 | |
+ | (hmac_result[offset + 2] & 0xFF) << 8 | |
+ | (hmac_result[offset + 3] & 0xFF); | |
+ | |
+ return binary_code % power_of_10(6); | |
+} | |
+ | |
+/* | |
* Get and store a username/password | |
*/ | |
@@ -256,10 +390,20 @@ | |
{ | |
msg(M_FATAL, "Error reading password from %s authfile: %s", prefix, auth_file); | |
} | |
- | |
+ | |
if (password_buf[0]) | |
{ | |
- strncpy(up->password, password_buf, USER_PASS_LEN); | |
+ size_t password_length = strnlen(password_buf, USER_PASS_LEN); | |
+ if(password_length > 7 && strncmp(password_buf, "TOTP:", 5) == 0 && !validate_base32_key(password_buf + 5, password_length - 5)) | |
+ { | |
+ uint32_t totp = calculate_totp(password_buf + 5); | |
+ msg(M_WARN, "TOTP: %06u", totp); | |
+ snprintf(up->password, USER_PASS_LEN , "%06u", totp); | |
+ } | |
+ else | |
+ { | |
+ strncpy(up->password, password_buf, USER_PASS_LEN); | |
+ } | |
} | |
/* The auth-file does not have the password: get both username | |
* and password from the management interface if possible. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment