Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Http proxy basic authentication on windows #1410

Closed
wants to merge 6 commits into from

Conversation

sylvlecl
Copy link
Contributor

@sylvlecl sylvlecl commented May 20, 2024

This is a proposition to solve the issue reported in this discussion.

Instead of just providing the content of HTTPS_PROXY environment variable, we now parse it to extract proxy credentials, then provide it to WinHTTP API (which is not as simple as one could expect).

I've been able to validate the behaviour in a test environment with a squid proxy.

Notes

  • use of regex may be overkill ...
  • In order to have the whole chain working, this would need to be ported to C executable tls12-download. Otherwise, windows users behind a proxy still need to download vcpkg.exe without the bootstrap script

Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
@sylvlecl sylvlecl changed the title Http proxy authentication on windows Http proxy basic authentication on windows May 20, 2024
@sylvlecl sylvlecl marked this pull request as ready for review May 20, 2024 17:57
@sylvlecl
Copy link
Contributor Author

@microsoft-github-policy-service agree company="RTE"

@guilpier-code
Copy link

guilpier-code commented May 22, 2024

I tried it and it worked.
Good job !
For hours I tried to make it work, even trying to involve my company's IT.
Thanks, it's being of great help


ProxyUrlParts parse_proxy_url(const std::wstring& url)
{
std::wregex scheme_pattern(L"^(.+://)(.*)"); // Matches scheme part
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Parsing URIs with regex is notoriously difficult and I'm not convinced that there wouldn't be ways to exploit this.
  2. We've tried really really hard to break the std::regex dependency entirely because all the std::regex implementations are awful.

Perhaps this means we should be looking at deploying something like trurl?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback!
Actually here it should be pretty easy to use simple string search instead of regex. I can implement this.

For "deploying trurl", would you mean adding a dependency to libcurl, on which trurl seems to rely behind the scene?
But I'm not sure it's easy to use on windows. I guess in that case it would make sense to completely rely on libcurl for the download.
Or do you mean something else, like requiring the user to install trurl in addition to vcpkg.exe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the regex use in last commit, to use only basic string searches.

@BillyONeal
Copy link
Member

  • In order to have the whole chain working, this would need to be ported to C executable tls12-download. Otherwise, windows users behind a proxy still need to download vcpkg.exe without the bootstrap script

tls12-download needs to be very very tiny in order to do its job since it needs to be checked in. Customers in this condition can manually download a vcpkg.exe with any browser they want.

Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
@sylvlecl sylvlecl requested a review from BillyONeal June 2, 2024 19:34
ProxyUrlParts{L"my-hostname.com:8080", ProxyCredentials{L"Joe", L"secret"}});
REQUIRE(parse_proxy_url(L"https://my-hostname.com:8080") == ProxyUrlParts{L"https://my-hostname.com:8080", {}});
REQUIRE(parse_proxy_url(L"https://Joe:secret@my-hostname.com:8080") ==
ProxyUrlParts{L"https://my-hostname.com:8080", ProxyCredentials{L"Joe", L"secret"}});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this should at least contain all the forms in the RFC: https://www.rfc-editor.org/rfc/rfc3986#section-1.1.2

Comment on lines +53 to +54
auto credentials_separator_pos = remaining_parts.find(L"@");
auto password_separator_pos = remaining_parts.find(L":");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems incorrect as the password separator, if present, must be inside the userinfo. Perhaps the STL algorithm form would be clearer since it makes chopping ranges into subranges like this easy:

auto user_info_end = std::find(remaining_parts.begin(), remaining_parts.end(), '@');
if (user_info_end != remaining_parts.end()) {
    auto password_separator = std::find(remaining_parts.begin(), user_info_end, ':');
    if (password_separator != user_info_end) {
        auto password_start = password_separator + 1;
        credentials = ProxyCredentials{std::wstring(remaining_parts.begin(), password_separator - remaining_parts.begin()),
        std::wstring(password_start, user_info_end - password_start)};
    }
}

Also, you want the character finds (L'@') not string finds (L"@").

Comment on lines 380 to +381
std::wstring env_proxy_settings = Strings::to_utf16(*p_https_proxy);
ProxyUrlParts parts = parse_proxy_url(env_proxy_settings);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be a good idea to make the parsing stuff etc. use chars rather than wchar_ts making that parsing code consistent with all other parsing code in the product.

constexpr auto npos = std::wstring::npos;
auto scheme_separator_pos = url.find(L"://");
std::wstring scheme;
std::wstring_view remaining_parts = url;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use StringView rather than std::wstring_view as we don't want to add a dependency on a new C++17 feature in order to have this. (Note that this would also require the char* rather than wchar_t* suggestion below)

explicit WinHttpContext(const T& value)
{
T* value_ptr = new T(value);
m_value_ptr_ptr = new T*(value_ptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaks a T if that new throws. Can you just use unique_ptr please?

Comment on lines +146 to +147
// which needs to be provided as a "DWORD_PTR" to WinHttpSendRequest
// (pointer to a pointer-sized variable).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DWORD_PTR is a pointer-sized variable itself. It models uintptr_t not uintptr_t*. You should be able to pass T* directly.

WINHTTP_NO_REQUEST_DATA,
0,
0,
ret.m_proxy_credentials.as_dword_ptr());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this data is tied to the lifetime of the request, is there any reason we don't just use an ordinary member variable and pass reinterpret_cast<DWORD_PTR>(this) for that?

Then set_proxy_credentials_on_redirect does reinterpret_cast<WinHttpRequest*>(pContext)->any data you want.

ProxyCredentials& ctxt = **ctxt_ptr_ptr;
WinHttpSetCredentials(hInternet,
WINHTTP_AUTH_TARGET_PROXY,
WINHTTP_AUTH_SCHEME_BASIC,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems very concerning to me to just arbitrarily staple basic auth creds when a redirect happens, in particular because basic auth is not safe to use without TLS. So if there is TLS for the first request, and that redirects to a not TLS place, stapling creds on again leaked the creds to the whole internet.

This kind of thing makes me extremely nervous to take this change. Perhaps the right behavior is that if we see proxy stuff that looks like it needs auth to always use curl.exe instead...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your concerns, I am clearly not a security expert. For sure relying on a proven solution would be more comfortable.

For the case you describe, I don't think this can happen: from my company network, the redirected request will go through the proxy again (it cannot go to the internet without going through the proxy), and I assume the proxy removes the credentials from the transferred request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed curl.exe seems to be widely present on windows at least in developer environments (comes with git), so it may be reasonable to rely on it!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already rely on it all over the place :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so I understand that just changing that condition (= use winhttp only if there are no headers, else use curl), to use curl also when we have credentials in the URL, would be the best solution ?

if (headers.size() == 0)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See alternative proposition based on this idea in #1434 : we can close this PR if it is indeed a better way to go.

@BillyONeal
Copy link
Member

Closing this PR because #1434 was merged and fixes the problem. Thanks!

@BillyONeal BillyONeal closed this Jul 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants