mirror of
https://github.com/OpenVPN/openvpn.git
synced 2025-05-09 13:41:06 +08:00
Protect cached username, password and token on client
Keep the memory segment containing username and password in "struct user_pass" encrypted. Works only on Windows. Username and auth-token cached by the server are not covered here. v2: Encrypt username and password separately as it looks more robust. We continue to depend on the username and password buffer sizes to be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE = 16, which is the case now. An error is logged if this is not the case. v3: move up ASSERT in auth_token.c Change-Id: I42e17e09a02f01aedadc2b03f9527967f6e1e8ff Signed-off-by: Selva Nair <selva.nair@gmail.com> Acked-by: Frank Lichtenheld <frank@lichtenheld.com> Message-Id: <20240906112908.1009-1-gert@greenie.muc.de> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg29079.html Signed-off-by: Gert Doering <gert@greenie.muc.de>
This commit is contained in:
parent
dbe7e45695
commit
12a9c357b6
@ -301,6 +301,7 @@ verify_auth_token(struct user_pass *up, struct tls_multi *multi,
|
||||
* Base64 is <= input and input is < USER_PASS_LEN, so using USER_PASS_LEN
|
||||
* is safe here but a bit overkill
|
||||
*/
|
||||
ASSERT(up && !up->protected);
|
||||
uint8_t b64decoded[USER_PASS_LEN];
|
||||
int decoded_len = openvpn_base64_decode(up->password + strlen(SESSION_ID_PREFIX),
|
||||
b64decoded, USER_PASS_LEN);
|
||||
|
@ -223,6 +223,7 @@ get_user_pass_cr(struct user_pass *up,
|
||||
bool password_from_stdin = false;
|
||||
bool response_from_stdin = true;
|
||||
|
||||
unprotect_user_pass(up);
|
||||
if (flags & GET_USER_PASS_PREVIOUS_CREDS_FAILED)
|
||||
{
|
||||
msg(M_WARN, "Note: previous '%s' credentials failed", prefix);
|
||||
@ -479,14 +480,18 @@ purge_user_pass(struct user_pass *up, const bool force)
|
||||
secure_memzero(up, sizeof(*up));
|
||||
up->nocache = nocache;
|
||||
}
|
||||
/*
|
||||
* don't show warning if the pass has been replaced by a token: this is an
|
||||
* artificial "auth-nocache"
|
||||
*/
|
||||
else if (!warn_shown)
|
||||
else
|
||||
{
|
||||
msg(M_WARN, "WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this");
|
||||
warn_shown = true;
|
||||
protect_user_pass(up);
|
||||
/*
|
||||
* don't show warning if the pass has been replaced by a token: this is an
|
||||
* artificial "auth-nocache"
|
||||
*/
|
||||
if (!warn_shown)
|
||||
{
|
||||
msg(M_WARN, "WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this");
|
||||
warn_shown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -495,6 +500,7 @@ set_auth_token(struct user_pass *tk, const char *token)
|
||||
{
|
||||
if (strlen(token))
|
||||
{
|
||||
unprotect_user_pass(tk);
|
||||
strncpynt(tk->password, token, USER_PASS_LEN);
|
||||
tk->token_defined = true;
|
||||
|
||||
@ -505,6 +511,7 @@ set_auth_token(struct user_pass *tk, const char *token)
|
||||
{
|
||||
tk->defined = true;
|
||||
}
|
||||
protect_user_pass(tk);
|
||||
}
|
||||
}
|
||||
|
||||
@ -513,6 +520,7 @@ set_auth_token_user(struct user_pass *tk, const char *username)
|
||||
{
|
||||
if (strlen(username))
|
||||
{
|
||||
unprotect_user_pass(tk);
|
||||
/* Clear the username before decoding to ensure no old material is left
|
||||
* and also allow decoding to not use all space to ensure the last byte is
|
||||
* always 0 */
|
||||
@ -523,6 +531,7 @@ set_auth_token_user(struct user_pass *tk, const char *username)
|
||||
{
|
||||
msg(D_PUSH, "Error decoding auth-token-username");
|
||||
}
|
||||
protect_user_pass(tk);
|
||||
}
|
||||
}
|
||||
|
||||
@ -779,3 +788,43 @@ prepend_dir(const char *dir, const char *path, struct gc_arena *gc)
|
||||
|
||||
return combined_path;
|
||||
}
|
||||
|
||||
void
|
||||
protect_user_pass(struct user_pass *up)
|
||||
{
|
||||
if (up->protected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (protect_buffer_win32(up->username, sizeof(up->username))
|
||||
&& protect_buffer_win32(up->password, sizeof(up->password)))
|
||||
{
|
||||
up->protected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
purge_user_pass(up, true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
unprotect_user_pass(struct user_pass *up)
|
||||
{
|
||||
if (!up->protected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (unprotect_buffer_win32(up->username, sizeof(up->username))
|
||||
&& unprotect_buffer_win32(up->password, sizeof(up->password)))
|
||||
{
|
||||
up->protected = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
purge_user_pass(up, true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ struct user_pass
|
||||
* use this second bool to track if the token (password) is defined */
|
||||
bool token_defined;
|
||||
bool nocache;
|
||||
bool protected;
|
||||
|
||||
/* max length of username/password */
|
||||
#ifdef ENABLE_PKCS11
|
||||
@ -207,6 +208,19 @@ void output_peer_info_env(struct env_set *es, const char *peer_info);
|
||||
struct buffer
|
||||
prepend_dir(const char *dir, const char *path, struct gc_arena *gc);
|
||||
|
||||
/**
|
||||
* Encrypt username and password buffers in user_pass
|
||||
*/
|
||||
void
|
||||
protect_user_pass(struct user_pass *up);
|
||||
|
||||
/**
|
||||
* Decrypt username and password buffers in user_pass
|
||||
*/
|
||||
void
|
||||
unprotect_user_pass(struct user_pass *up);
|
||||
|
||||
|
||||
#define _STRINGIFY(S) #S
|
||||
/* *INDENT-OFF* - uncrustify need to ignore this macro */
|
||||
#define MAC_FMT _STRINGIFY(%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx)
|
||||
|
@ -291,13 +291,14 @@ get_user_pass_http(struct http_proxy_info *p, const bool force)
|
||||
UP_TYPE_PROXY,
|
||||
flags);
|
||||
static_proxy_user_pass.nocache = p->options.nocache;
|
||||
protect_user_pass(&static_proxy_user_pass);
|
||||
}
|
||||
|
||||
/*
|
||||
* Using cached credentials
|
||||
*/
|
||||
p->queried_creds = true;
|
||||
p->up = static_proxy_user_pass;
|
||||
p->up = static_proxy_user_pass; /* this is a copy of protected memory */
|
||||
}
|
||||
|
||||
#if 0
|
||||
@ -668,6 +669,7 @@ establish_http_proxy_passthru(struct http_proxy_info *p,
|
||||
{
|
||||
clear_user_pass_http();
|
||||
}
|
||||
unprotect_user_pass(&p->up);
|
||||
}
|
||||
|
||||
/* are we being called again after getting the digest server nonce in the previous transaction? */
|
||||
|
@ -243,6 +243,7 @@ static struct user_pass passbuf; /* GLOBAL */
|
||||
void
|
||||
pem_password_setup(const char *auth_file)
|
||||
{
|
||||
unprotect_user_pass(&passbuf);
|
||||
if (!strlen(passbuf.password))
|
||||
{
|
||||
get_user_pass(&passbuf, auth_file, UP_TYPE_PRIVATE_KEY, GET_USER_PASS_MANAGEMENT|GET_USER_PASS_PASSWORD_ONLY);
|
||||
@ -256,6 +257,7 @@ pem_password_callback(char *buf, int size, int rwflag, void *u)
|
||||
{
|
||||
/* prompt for password even if --askpass wasn't specified */
|
||||
pem_password_setup(NULL);
|
||||
ASSERT(!passbuf.protected);
|
||||
strncpynt(buf, passbuf.password, size);
|
||||
purge_user_pass(&passbuf, false);
|
||||
|
||||
@ -295,6 +297,7 @@ auth_user_pass_setup(const char *auth_file, bool is_inline,
|
||||
|
||||
if (!auth_user_pass.defined && !auth_token.defined)
|
||||
{
|
||||
unprotect_user_pass(&auth_user_pass);
|
||||
#ifdef ENABLE_MANAGEMENT
|
||||
if (auth_challenge) /* dynamic challenge/response */
|
||||
{
|
||||
@ -2094,6 +2097,7 @@ key_method_2_write(struct buffer *buf, struct tls_multi *multi, struct tls_sessi
|
||||
{
|
||||
up = &auth_token;
|
||||
}
|
||||
unprotect_user_pass(up);
|
||||
|
||||
if (!write_string(buf, up->username, -1))
|
||||
{
|
||||
@ -2106,8 +2110,11 @@ key_method_2_write(struct buffer *buf, struct tls_multi *multi, struct tls_sessi
|
||||
/* save username for auth-token which may get pushed later */
|
||||
if (session->opt->pull && up != &auth_token)
|
||||
{
|
||||
unprotect_user_pass(&auth_token);
|
||||
strncpynt(auth_token.username, up->username, USER_PASS_LEN);
|
||||
protect_user_pass(&auth_token);
|
||||
}
|
||||
protect_user_pass(up);
|
||||
/* respect auth-nocache */
|
||||
purge_user_pass(&auth_user_pass, false);
|
||||
}
|
||||
|
@ -1594,6 +1594,8 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi,
|
||||
{
|
||||
struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key */
|
||||
|
||||
ASSERT(up && !up->protected);
|
||||
|
||||
#ifdef ENABLE_MANAGEMENT
|
||||
int man_def_auth = KMDA_UNDEF;
|
||||
|
||||
|
@ -1658,4 +1658,40 @@ plugin_in_trusted_dir(const WCHAR *plugin_path)
|
||||
return wcsnicmp(system_dir, plugin_path, wcslen(system_dir)) == 0;
|
||||
}
|
||||
|
||||
bool
|
||||
protect_buffer_win32(char *buf, size_t len)
|
||||
{
|
||||
bool ret;
|
||||
if (len % CRYPTPROTECTMEMORY_BLOCK_SIZE)
|
||||
{
|
||||
msg(M_NONFATAL, "Error: Unable to encrypt memory: buffer size not a multiple of %d",
|
||||
CRYPTPROTECTMEMORY_BLOCK_SIZE);
|
||||
return false;
|
||||
}
|
||||
ret = CryptProtectMemory(buf, len, CRYPTPROTECTMEMORY_SAME_PROCESS);
|
||||
if (!ret)
|
||||
{
|
||||
msg(M_NONFATAL | M_ERRNO, "Failed to encrypt memory.");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
unprotect_buffer_win32(char *buf, size_t len)
|
||||
{
|
||||
bool ret;
|
||||
if (len % CRYPTPROTECTMEMORY_BLOCK_SIZE)
|
||||
{
|
||||
msg(M_NONFATAL, "Error: Unable to decrypt memory: buffer size not a multiple of %d",
|
||||
CRYPTPROTECTMEMORY_BLOCK_SIZE);
|
||||
return false;
|
||||
}
|
||||
ret = CryptUnprotectMemory(buf, len, CRYPTPROTECTMEMORY_SAME_PROCESS);
|
||||
if (!ret)
|
||||
{
|
||||
msg(M_FATAL | M_ERRNO, "Failed to decrypt memory.");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* ifdef _WIN32 */
|
||||
|
@ -351,5 +351,27 @@ get_openvpn_reg_value(const WCHAR *key, WCHAR *value, DWORD size);
|
||||
bool
|
||||
plugin_in_trusted_dir(const WCHAR *plugin_path);
|
||||
|
||||
/**
|
||||
* Encrypt a region of memory using CryptProtectMemory()
|
||||
* with access restricted to the current process.
|
||||
*
|
||||
* - buf pointer to the memory
|
||||
* - len number of bytes to encrypt -- must be a multiple of
|
||||
* CRYPTPROTECTMEMORY_BLOCK_SIZE = 16
|
||||
*/
|
||||
bool
|
||||
protect_buffer_win32(char *buf, size_t len);
|
||||
|
||||
/**
|
||||
* Decrypt a previously encrypted region of memory using CryptUnProtectMemory()
|
||||
* with access restricted to the current process.
|
||||
*
|
||||
* - buf pointer to the memory
|
||||
* - len number of bytes to encrypt -- must be a multiple of
|
||||
* CRYPTPROTECTMEMORY_BLOCK_SIZE = 16
|
||||
*/
|
||||
bool
|
||||
unprotect_buffer_win32(char *buf, size_t len);
|
||||
|
||||
#endif /* ifndef OPENVPN_WIN32_H */
|
||||
#endif /* ifdef _WIN32 */
|
||||
|
@ -83,6 +83,18 @@ parse_line(const char *line, char **p, const int n, const char *file,
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
protect_buffer_win32(char *buf, size_t len)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
unprotect_buffer_win32(char *buf, size_t len)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* tooling */
|
||||
static void
|
||||
reset_user_pass(struct user_pass *up)
|
||||
|
Loading…
x
Reference in New Issue
Block a user