Skip to content

Instantly share code, notes, and snippets.

@pccr10001
Created October 3, 2024 14:30
Show Gist options
  • Save pccr10001/27ccd522d6fd03c2530e18a675ad94e9 to your computer and use it in GitHub Desktop.
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 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