@@ -301,6 +301,7 @@
* 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 @@
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 @@
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 @@
{
if (strlen(token))
{
+ unprotect_user_pass(tk);
strncpynt(tk->password, token, USER_PASS_LEN);
tk->token_defined = true;
@@ -505,6 +511,7 @@
{
tk->defined = true;
}
+ protect_user_pass(tk);
}
}
@@ -513,6 +520,7 @@
{
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 @@
{
msg(D_PUSH, "Error decoding auth-token-username");
}
+ protect_user_pass(tk);
}
}
@@ -779,3 +788,43 @@
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 @@
* 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 @@
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)
@@ -289,13 +289,14 @@
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
@@ -666,6 +667,7 @@
{
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 @@
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 @@
{
/* 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 @@
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 @@
{
up = &auth_token;
}
+ unprotect_user_pass(up);
if (!write_string(buf, up->username, -1))
{
@@ -2106,8 +2110,11 @@
/* 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 @@
{
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 @@
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 @@
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 @@
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)