const char *auth_token_pem_name = "OpenVPN auth-token server key";
#define AUTH_TOKEN_SESSION_ID_LEN 12
-#define AUTH_TOKEN_SESSION_ID_BASE64_LEN (AUTH_TOKEN_SESSION_ID_LEN * 8 / 6)
+#define AUTH_TOKEN_SESSION_ID_BASE64_LEN (OPENVPN_BASE64_LENGTH(AUTH_TOKEN_SESSION_ID_LEN))
-#if AUTH_TOKEN_SESSION_ID_LEN % 3
-#error AUTH_TOKEN_SESSION_ID_LEN needs to be multiple a 3
-#endif
+/* We want our token to be a multiple of 3 bytes to avoid the base64 padding */
+static_assert(AUTH_TOKEN_SESSION_ID_LEN % 3 == 0, "AUTH_TOKEN_SESSION_ID_LEN needs to be multiple a 3");
+#define AUTH_TOKEN_HMAC_LEN SHA256_DIGEST_LENGTH
/* Size of the data of the token (not b64 encoded and without prefix) */
-#define TOKEN_DATA_LEN (2 * sizeof(int64_t) + AUTH_TOKEN_SESSION_ID_LEN + 32)
+#define TOKEN_DATA_LEN (2 * sizeof(int64_t) + AUTH_TOKEN_SESSION_ID_LEN + AUTH_TOKEN_HMAC_LEN)
+#define TOKEN_DATA_BASE64_LEN (OPENVPN_BASE64_LENGTH(TOKEN_DATA_LEN))
+
+
+#define TOTAL_SESSION_TOKEN_LEN (strlen(SESSION_ID_PREFIX) + TOKEN_DATA_BASE64_LEN)
+
+/* Ensure that TOKEN_DATA_LEN is a multiple of 3 so the we avoid the base64
+ * padding */
+static_assert(TOKEN_DATA_LEN % 3 == 0, "TOKEN_DATA_BASE64_LEN is not a multiple of 3");
+
+bool
+is_auth_token(const char *password)
+{
+ if (strlen(password) != TOTAL_SESSION_TOKEN_LEN)
+ {
+ return false;
+ }
+
+ return (memcmp_constant_time(SESSION_ID_PREFIX, password, strlen(SESSION_ID_PREFIX)) == 0);
+}
static struct key_type
auth_token_kt(void)
* and being a multiple of 4 ensure that it a multiple of bytes
* in the encoding
*/
-
char session_id[AUTH_TOKEN_SESSION_ID_LEN * 2] = { 0 };
memcpy(session_id, session_id_source + strlen(SESSION_ID_PREFIX),
AUTH_TOKEN_SESSION_ID_LEN * 8 / 6);
if (multi->auth_token_initial)
{
- /* Just enough space to fit 8 bytes+ 1 extra to decode a non-padded
+ /* Just enough space to fit 8 bytes + 1 extra to decode a non-padded
* base64 string (multiple of 3 bytes). 9 bytes => 12 bytes base64
* bytes
*/
char *initial_token_copy = string_alloc(multi->auth_token_initial, &gc);
char *old_sessid = initial_token_copy + strlen(SESSION_ID_PREFIX);
- char *old_tstamp_initial = old_sessid + AUTH_TOKEN_SESSION_ID_LEN * 8 / 6;
+ char *old_tstamp_initial = old_sessid + AUTH_TOKEN_SESSION_ID_BASE64_LEN;
/*
* We null terminate the old token just after the session ID to let
hmac_ctx_final(ctx, hmac_output);
const uint8_t *hmac = b64decoded + TOKEN_DATA_LEN - 256 / 8;
- return memcmp_constant_time(&hmac_output, hmac, 32) == 0;
+ return memcmp_constant_time(&hmac_output, hmac, AUTH_TOKEN_HMAC_LEN) == 0;
}
unsigned int
static const char *random_token =
"SESS_ID_AT_ThhRItzOKNKrh3dfAAAAAFwzHpwAAAAAXDMenDdrq0RoH3dkA1f7O3wO+7kZcx2DusVZrRmFlWQM9HOb";
+static const char *last_session_state_value;
+static char last_session_id_value[64];
static int
setup(void **state)
init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, "TEST");
now = 0;
+
+ CLEAR(last_session_id_value);
+ last_session_state_value = NULL;
+
return 0;
}
assert_int_equal(verify_auth_token(&ctx->up, &ctx->multi, ctx->session), AUTH_TOKEN_HMAC_OK);
}
-static const char *lastsesion_statevalue;
+
void
setenv_str(struct env_set *es, const char *name, const char *value)
{
if (streq(name, "session_state"))
{
- lastsesion_statevalue = value;
+ last_session_state_value = value;
+ }
+ else if (streq(name, "session_id"))
+ {
+ strncpy(last_session_id_value, value, sizeof(last_session_id_value));
}
}
ks->auth_token_state_flags = 0;
ctx->multi.auth_token = NULL;
add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
- assert_string_equal(lastsesion_statevalue, "Initial");
+ assert_string_equal(last_session_state_value, "Initial");
+ /* This generated a fresh token with a new session ID,
+ * check that this session id is part of the multi initial token and
+ * found at the right index */
+ char *idx = strstr(ctx->multi.auth_token_initial, last_session_id_value);
+ assert_non_null(idx);
+
+ ptrdiff_t offset = idx - ctx->multi.auth_token_initial;
+ assert_int_equal(offset, 11);
+
+ CLEAR(last_session_id_value);
ks->auth_token_state_flags = 0;
- strcpy(ctx->up.password, now0key0);
+ strcpy(ctx->up.password, "somepassword");
+
+ free(ctx->multi.auth_token_initial);
+ ctx->multi.auth_token_initial = strdup(now0key0);
+
add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
- assert_string_equal(lastsesion_statevalue, "Invalid");
+ assert_string_equal(last_session_state_value, "Initial");
+ assert_string_equal(last_session_id_value, "0123456789abcdef");
+
+ CLEAR(last_session_id_value);
+ free(ctx->multi.auth_token_initial);
+ ctx->multi.auth_token_initial = NULL;
ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK;
+ strcpy(ctx->up.password, now0key0);
add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
- assert_string_equal(lastsesion_statevalue, "Authenticated");
+ assert_string_equal(last_session_state_value, "Authenticated");
+ assert_string_equal(last_session_id_value, "0123456789abcdef");
ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED;
add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
- assert_string_equal(lastsesion_statevalue, "Expired");
+ assert_string_equal(last_session_state_value, "Expired");
ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_VALID_EMPTYUSER;
add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
- assert_string_equal(lastsesion_statevalue, "AuthenticatedEmptyUser");
+ assert_string_equal(last_session_state_value, "AuthenticatedEmptyUser");
ks->auth_token_state_flags =
AUTH_TOKEN_HMAC_OK | AUTH_TOKEN_EXPIRED | AUTH_TOKEN_VALID_EMPTYUSER;
add_session_token_env(ctx->session, &ctx->multi, &ctx->up);
- assert_string_equal(lastsesion_statevalue, "ExpiredEmptyUser");
+ assert_string_equal(last_session_state_value, "ExpiredEmptyUser");
}
static void
}
+static void
+auth_token_test_is_auth_token(void **state)
+{
+ /* Known-good tokens are recognised */
+ assert_true(is_auth_token(now0key0));
+ assert_true(is_auth_token(random_token));
+
+ /* Empty and clearly too-short strings are rejected */
+ assert_false(is_auth_token(""));
+ assert_false(is_auth_token("foobar"));
+ assert_false(is_auth_token(SESSION_ID_PREFIX));
+ assert_false(is_auth_token("SESS_ID_AT_Something_wild"));
+
+ /* Since all auth token have the same length, get the length from
+ * now0key0 */
+ const size_t auth_token_len = strlen(now0key0);
+
+ /* Right length, incorrect prefix */
+ char buf[256] = { 0 };
+ assert_true(auth_token_len < sizeof(buf));
+ memset(buf, 'A', auth_token_len);
+ buf[auth_token_len] = '\0';
+ assert_false(is_auth_token(buf));
+
+ /* Correct prefix to ensure the previous test is valid */
+ memcpy(buf, "SESS_ID_AT_", strlen("SESS_ID_AT_"));
+ assert_true(is_auth_token(buf));
+
+ /* Correct prefix, one byte too short */
+ buf[auth_token_len - 1] = '\0';
+ assert_false(is_auth_token(buf));
+
+ /* Correct prefix, one byte too long */
+ strcpy(buf, now0key0);
+ buf[auth_token_len] = 'A';
+ buf[auth_token_len + 1] = '\0';
+ assert_false(is_auth_token(buf));
+}
+
static void
auth_token_test_key_load(void **state)
{
cmocka_unit_test_setup_teardown(auth_token_test_random_keys, setup, teardown),
cmocka_unit_test_setup_teardown(auth_token_test_key_load, setup, teardown),
cmocka_unit_test_setup_teardown(auth_token_test_timeout, setup, teardown),
- cmocka_unit_test_setup_teardown(auth_token_test_session_mismatch, setup, teardown)
+ cmocka_unit_test_setup_teardown(auth_token_test_session_mismatch, setup, teardown),
+ cmocka_unit_test(auth_token_test_is_auth_token),
};
return cmocka_run_group_tests_name("auth-token tests", tests, NULL, NULL);