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:
Selva Nair 2024-09-06 13:29:08 +02:00 committed by Gert Doering
parent dbe7e45695
commit 12a9c357b6
9 changed files with 153 additions and 8 deletions

View File

@ -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);

View File

@ -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
}

View File

@ -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)

View File

@ -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? */

View File

@ -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);
}

View File

@ -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;

View File

@ -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 */

View File

@ -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 */

View File

@ -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)