diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 25d42a04aed5e..d8431b2b4ca7c 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -70,6 +70,10 @@ minor_behavior_changes: change: | :ref:`Credential injector filter ` is no longer a work in progress field. +- area: oauth2 + change: | + The access token, id token and refresh token in the cookies are now encrypted using the HMAC secret. This behavior can + be reverted by setting the runtime guard ``envoy.reloadable_features.oauth2_encrypt_tokens`` to ``false``. - area: http3 change: | Validate HTTP/3 pseudo headers. Can be disabled by setting ``envoy.restart_features.validate_http3_pseudo_headers`` to false. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index a311bb5d18434..69b0504b650cc 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -65,6 +65,7 @@ RUNTIME_GUARD(envoy_reloadable_features_local_reply_traverses_filter_chain_after RUNTIME_GUARD(envoy_reloadable_features_mmdb_files_reload_enabled); RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name); RUNTIME_GUARD(envoy_reloadable_features_normalize_rds_provider_config); +RUNTIME_GUARD(envoy_reloadable_features_oauth2_encrypt_tokens); RUNTIME_GUARD(envoy_reloadable_features_oauth2_use_refresh_token); RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout); RUNTIME_GUARD(envoy_reloadable_features_original_src_fix_port_exhaustion); diff --git a/source/extensions/filters/http/oauth2/filter.cc b/source/extensions/filters/http/oauth2/filter.cc index 872e101814ba0..2b78958cf6c3e 100644 --- a/source/extensions/filters/http/oauth2/filter.cc +++ b/source/extensions/filters/http/oauth2/filter.cc @@ -524,7 +524,7 @@ void OAuth2CookieValidator::setParams(const Http::RequestHeaderMap& headers, }); expires_ = findValue(cookies, cookie_names_.oauth_expires_); - token_ = findValue(cookies, cookie_names_.bearer_token_); + access_token_ = findValue(cookies, cookie_names_.bearer_token_); id_token_ = findValue(cookies, cookie_names_.id_token_); refresh_token_ = findValue(cookies, cookie_names_.refresh_token_); hmac_ = findValue(cookies, cookie_names_.oauth_hmac_); @@ -540,9 +540,9 @@ bool OAuth2CookieValidator::hmacIsValid() const { if (!cookie_domain_.empty()) { cookie_domain = cookie_domain_; } - return ((encodeHmacBase64(secret_, cookie_domain, expires_, token_, id_token_, refresh_token_) == - hmac_) || - (encodeHmacHexBase64(secret_, cookie_domain, expires_, token_, id_token_, + return ((encodeHmacBase64(secret_, cookie_domain, expires_, access_token_, id_token_, + refresh_token_) == hmac_) || + (encodeHmacHexBase64(secret_, cookie_domain, expires_, access_token_, id_token_, refresh_token_) == hmac_)); } @@ -578,6 +578,12 @@ OAuth2Filter::OAuth2Filter(FilterConfigSharedPtr config, * 5) user is unauthorized */ Http::FilterHeadersStatus OAuth2Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool) { + // Decrypt the OAuth tokens and update the OAuth tokens in the request headers before forwarding + // the request upstream. + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.oauth2_encrypt_tokens")) { + decryptAndUpdateOAuthTokenCookies(headers); + } + // Skip Filter and continue chain if a Passthrough header is matching // Must be done before the sanitation of the authorization header, // otherwise the authorization header might be altered or removed @@ -713,7 +719,8 @@ Http::FilterHeadersStatus OAuth2Filter::decodeHeaders(Http::RequestHeaderMap& he DecryptResult decrypt_result = decrypt(encrypted_code_verifier, config_->hmacSecret()); if (decrypt_result.error.has_value()) { - ENVOY_LOG(error, "decryption failed: {}", decrypt_result.error.value()); + ENVOY_LOG(error, "failed to decrypt code verifier: {}, error: {}", encrypted_code_verifier, + decrypt_result.error.value()); sendUnauthorizedResponse(); return Http::FilterHeadersStatus::StopIteration; } @@ -754,6 +761,66 @@ bool OAuth2Filter::canSkipOAuth(Http::RequestHeaderMap& headers) const { return false; } +// Decrypt the OAuth tokens and updates the OAuth tokens in the request cookies before forwarding +// the request upstream. +void OAuth2Filter::decryptAndUpdateOAuthTokenCookies(Http::RequestHeaderMap& headers) const { + absl::flat_hash_map cookies = Http::Utility::parseCookies(headers); + if (cookies.empty()) { + return; + } + + const CookieNames& cookie_names = config_->cookieNames(); + + const std::string encrypted_access_token = findValue(cookies, cookie_names.bearer_token_); + const std::string encrypted_id_token = findValue(cookies, cookie_names.id_token_); + const std::string encrypted_refresh_token = findValue(cookies, cookie_names.refresh_token_); + + if (!encrypted_access_token.empty()) { + cookies.insert_or_assign(cookie_names.bearer_token_, decryptToken(encrypted_access_token)); + } + + if (!encrypted_id_token.empty()) { + cookies.insert_or_assign(cookie_names.id_token_, decryptToken(encrypted_id_token)); + } + + if (!encrypted_refresh_token.empty()) { + cookies.insert_or_assign(cookie_names.refresh_token_, decryptToken(encrypted_refresh_token)); + } + + if (!encrypted_access_token.empty() || !encrypted_id_token.empty() || + !encrypted_refresh_token.empty()) { + std::string new_cookies(absl::StrJoin(cookies, "; ", absl::PairFormatter("="))); + headers.setReferenceKey(Http::Headers::get().Cookie, new_cookies); + } +} + +std::string OAuth2Filter::encryptToken(const std::string& token) const { + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.oauth2_encrypt_tokens")) { + return encrypt(token, config_->hmacSecret(), random_); + } + return token; +} + +std::string OAuth2Filter::decryptToken(const std::string& encrypted_token) const { + if (encrypted_token.empty()) { + return EMPTY_STRING; + } + + DecryptResult decrypt_result = decrypt(encrypted_token, config_->hmacSecret()); + if (decrypt_result.error.has_value()) { + ENVOY_LOG(error, "failed to decrypt token: {}, error: {}", encrypted_token, + decrypt_result.error.value()); + // There are two cases: + // 1. The token is a legacy unencrypted token. + // In this case, we return the token as-is to allow the request to proceed. + // 2. The token is encrypted, but the decryption failed due to the HMAC secret is changed. + // In this case, we return the original encrypted token, the HMAC validation will fail + // and the user will be redirected to the OAuth server for re-authentication. + return encrypted_token; + } + return decrypt_result.plaintext; +} + bool OAuth2Filter::canRedirectToOAuthServer(Http::RequestHeaderMap& headers) const { for (const auto& matcher : config_->denyRedirectMatchers()) { if (matcher->matchesHeaders(headers)) { @@ -827,9 +894,6 @@ void OAuth2Filter::redirectToOAuthServer(Http::RequestHeaderMap& headers) { // Generate a PKCE code verifier and challenge for the OAuth flow. const std::string code_verifier = generateCodeVerifier(random_); - // Encrypt the code verifier, using HMAC secret as the symmetric key. - const std::string encrypted_code_verifier = - encrypt(code_verifier, config_->hmacSecret(), random_); const std::chrono::seconds code_verifier_token_expires_in = config_->getCodeVerifierTokenExpiresIn(); @@ -842,9 +906,10 @@ void OAuth2Filter::redirectToOAuthServer(Http::RequestHeaderMap& headers) { cookie_tail_http_only = absl::StrCat( fmt::format(CookieDomainFormatString, config_->cookieDomain()), cookie_tail_http_only); } - response_headers->addReferenceKey(Http::Headers::get().SetCookie, - absl::StrCat(config_->cookieNames().code_verifier_, "=", - encrypted_code_verifier, cookie_tail_http_only)); + response_headers->addReferenceKey( + Http::Headers::get().SetCookie, + absl::StrCat(config_->cookieNames().code_verifier_, "=", + encrypt(code_verifier, config_->hmacSecret(), random_), cookie_tail_http_only)); const std::string code_challenge = generateCodeChallenge(code_verifier); query_params.overwrite(queryParamsCodeChallenge, code_challenge); @@ -866,7 +931,7 @@ void OAuth2Filter::redirectToOAuthServer(Http::RequestHeaderMap& headers) { /** * Modifies the state of the filter by adding response headers to the decoder_callbacks */ -Http::FilterHeadersStatus OAuth2Filter::signOutUser(const Http::RequestHeaderMap& headers) { +Http::FilterHeadersStatus OAuth2Filter::signOutUser(const Http::RequestHeaderMap& headers) const { Http::ResponseHeaderMapPtr response_headers{Http::createHeaderMap( {{Http::Headers::get().Status, std::to_string(enumToInt(Http::Code::Found))}})}; std::string cookie_domain; @@ -1163,7 +1228,8 @@ void OAuth2Filter::addResponseCookies(Http::ResponseHeaderMap& headers, if (!access_token_.empty()) { headers.addReferenceKey(Http::Headers::get().SetCookie, - absl::StrCat(cookie_names.bearer_token_, "=", access_token_, + absl::StrCat(cookie_names.bearer_token_, "=", + encryptToken(access_token_), BuildCookieTail(1))); // BEARER_TOKEN } else if (request_cookies.contains(cookie_names.bearer_token_)) { headers.addReferenceKey( @@ -1173,9 +1239,9 @@ void OAuth2Filter::addResponseCookies(Http::ResponseHeaderMap& headers, } if (!id_token_.empty()) { - headers.addReferenceKey( - Http::Headers::get().SetCookie, - absl::StrCat(cookie_names.id_token_, "=", id_token_, BuildCookieTail(4))); // ID_TOKEN + headers.addReferenceKey(Http::Headers::get().SetCookie, + absl::StrCat(cookie_names.id_token_, "=", encryptToken(id_token_), + BuildCookieTail(4))); // ID_TOKEN } else if (request_cookies.contains(cookie_names.id_token_)) { headers.addReferenceKey( Http::Headers::get().SetCookie, @@ -1185,7 +1251,8 @@ void OAuth2Filter::addResponseCookies(Http::ResponseHeaderMap& headers, if (!refresh_token_.empty()) { headers.addReferenceKey(Http::Headers::get().SetCookie, - absl::StrCat(cookie_names.refresh_token_, "=", refresh_token_, + absl::StrCat(cookie_names.refresh_token_, "=", + encryptToken(refresh_token_), BuildCookieTail(5))); // REFRESH_TOKEN } else if (request_cookies.contains(cookie_names.refresh_token_)) { headers.addReferenceKey( @@ -1206,8 +1273,9 @@ void OAuth2Filter::sendUnauthorizedResponse() { // * Does the query parameters contain the code and state? // * Does the state contain the original request URL and the CSRF token? // * Does the CSRF token in the state match the one in the cookie? -CallbackValidationResult OAuth2Filter::validateOAuthCallback(const Http::RequestHeaderMap& headers, - const absl::string_view path_str) { +CallbackValidationResult +OAuth2Filter::validateOAuthCallback(const Http::RequestHeaderMap& headers, + const absl::string_view path_str) const { // Return 401 unauthorized if the query parameters contain an error response. const auto query_parameters = Http::Utility::QueryParamsMulti::parseQueryString(path_str); if (query_parameters.getFirstValue(queryParamsError).has_value()) { diff --git a/source/extensions/filters/http/oauth2/filter.h b/source/extensions/filters/http/oauth2/filter.h index e3e17defe961e..b4b43a0574607 100644 --- a/source/extensions/filters/http/oauth2/filter.h +++ b/source/extensions/filters/http/oauth2/filter.h @@ -268,13 +268,13 @@ class CookieValidator { virtual bool canUpdateTokenByRefreshToken() const PURE; }; -class OAuth2CookieValidator : public CookieValidator { +class OAuth2CookieValidator : public CookieValidator, Logger::Loggable { public: explicit OAuth2CookieValidator(TimeSource& time_source, const CookieNames& cookie_names, const std::string& cookie_domain) : time_source_(time_source), cookie_names_(cookie_names), cookie_domain_(cookie_domain) {} - const std::string& token() const override { return token_; } + const std::string& token() const override { return access_token_; } const std::string& refreshToken() const override { return refresh_token_; } void setParams(const Http::RequestHeaderMap& headers, const std::string& secret) override; @@ -284,7 +284,7 @@ class OAuth2CookieValidator : public CookieValidator { bool canUpdateTokenByRefreshToken() const override; private: - std::string token_; + std::string access_token_; std::string id_token_; std::string refresh_token_; std::string expires_; @@ -368,7 +368,7 @@ class OAuth2Filter : public Http::PassThroughFilter, bool canRedirectToOAuthServer(Http::RequestHeaderMap& headers) const; void redirectToOAuthServer(Http::RequestHeaderMap& headers); - Http::FilterHeadersStatus signOutUser(const Http::RequestHeaderMap& headers); + Http::FilterHeadersStatus signOutUser(const Http::RequestHeaderMap& headers) const; std::string getEncodedToken() const; std::string getExpiresTimeForRefreshToken(const std::string& refresh_token, @@ -379,9 +379,12 @@ class OAuth2Filter : public Http::PassThroughFilter, void addResponseCookies(Http::ResponseHeaderMap& headers, const std::string& encoded_token) const; const std::string& bearerPrefix() const; CallbackValidationResult validateOAuthCallback(const Http::RequestHeaderMap& headers, - const absl::string_view path_str); + const absl::string_view path_str) const; bool validateCsrfToken(const Http::RequestHeaderMap& headers, const std::string& csrf_token) const; + void decryptAndUpdateOAuthTokenCookies(Http::RequestHeaderMap& headers) const; + std::string encryptToken(const std::string& token) const; + std::string decryptToken(const std::string& encrypted_token) const; }; } // namespace Oauth2 diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index a6ac5b5d63a9a..8a05660cfecdc 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -48,6 +48,13 @@ static const std::string TEST_CODE_VERIFIER = "Fc1bBwAAAAAVzVsHAAAAABXNWwcAAAAAF static const std::string TEST_ENCRYPTED_CODE_VERIFIER = "Fc1bBwAAAAAVzVsHAAAAABjf6i_Hvf8T2dEuEhPhhDNMlp16az-0dxisL-TzJKaZjOMF8nov_pG377FHmpKcsA"; static const std::string TEST_CODE_CHALLENGE = "YRQaBq_UpkWzfr6JvtNnh7LMfmPVcIKVYdV98ugwmLY"; +static const std::string TEST_ENCRYPTED_ACCESS_TOKEN = + "Fc1bBwAAAAAVzVsHAAAAAHDCo6XWwdgw5IYsxjfymIQ"; //"access_code" +static const std::string TEST_ENCRYPTED_ID_TOKEN = + "Fc1bBwAAAAAVzVsHAAAAAJohQ-XDfnYLdgIQ2yJfRZQ"; //"some-id-token" +static const std::string TEST_ENCRYPTED_REFRESH_TOKEN = + "Fc1bBwAAAAAVzVsHAAAAAERBBlyQ3ASXvDHzyIRDhLwvl1w07AKhjwBz1s4wJGX8"; //"some-refresh-token" +static const std::string TEST_HMAC_SECRET = "asdf_token_secret_fdsa"; namespace { Http::RegisterCustomInlineHeader @@ -60,7 +67,7 @@ class MockSecretReader : public SecretReader { CONSTRUCT_ON_FIRST_USE(std::string, "asdf_client_secret_fdsa"); } const std::string& hmacSecret() const override { - CONSTRUCT_ON_FIRST_USE(std::string, "asdf_token_secret_fdsa"); + CONSTRUCT_ON_FIRST_USE(std::string, TEST_HMAC_SECRET); } }; @@ -251,7 +258,7 @@ class OAuth2Test : public testing::TestWithParam { // Validates the behavior of the cookie validator. void expectValidCookies(const CookieNames& cookie_names, const std::string& cookie_domain) { // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. - test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); + test_time_.setSystemTime(SystemTime(std::chrono::seconds(1000))); const auto expires_at_s = DateUtil::nowToSeconds(test_time_.timeSystem()) + 10; @@ -261,16 +268,17 @@ class OAuth2Test : public testing::TestWithParam { {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, {Http::Headers::get().Cookie.get(), fmt::format("{}={}", cookie_names.oauth_expires_, expires_at_s)}, - {Http::Headers::get().Cookie.get(), absl::StrCat(cookie_names.bearer_token_, "=xyztoken")}, {Http::Headers::get().Cookie.get(), - absl::StrCat(cookie_names.oauth_hmac_, "=dCu0otMcLoaGF73jrT+R8rGA0pnWyMgNf4+GivGrHEI=")}, + absl::StrCat(cookie_names.bearer_token_, "=" + TEST_ENCRYPTED_ACCESS_TOKEN)}, + {Http::Headers::get().Cookie.get(), + absl::StrCat(cookie_names.oauth_hmac_, "=oMh0+qk68Y4ya4JGQqT+Ja1Y1X58Sc8iATRxPPPG5Yc=")}, }; auto cookie_validator = std::make_shared(test_time_, cookie_names, cookie_domain); EXPECT_EQ(cookie_validator->token(), ""); EXPECT_EQ(cookie_validator->refreshToken(), ""); - cookie_validator->setParams(request_headers, "mock-secret"); + cookie_validator->setParams(request_headers, TEST_HMAC_SECRET); EXPECT_TRUE(cookie_validator->hmacIsValid()); EXPECT_TRUE(cookie_validator->timestampIsValid()); @@ -987,6 +995,64 @@ TEST_F(OAuth2Test, SetBearerToken) { // std::string legit_token{"legit_token"}; // EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); + EXPECT_CALL(*oauth_client_, asyncGetAccessToken("123", TEST_CLIENT_ID, "asdf_client_secret_fdsa", + "https://traffic.example.com" + TEST_CALLBACK, + TEST_CODE_VERIFIER, AuthType::UrlEncodedBody)); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, + filter_->decodeHeaders(request_headers, false)); + + // Expected response after the callback & validation is complete - verifying we kept the + // state and method of the original request, including the query string parameters. + Http::TestRequestHeaderMapImpl response_headers{ + {Http::Headers::get().Status.get(), "302"}, + {Http::Headers::get().SetCookie.get(), "OauthHMAC=" + "4TKyxPV/F7yyvr0XgJ2bkWFOc8t4IOFen1k29b84MAQ=;" + "path=/;Max-Age=600;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + ";path=/;Max-Age=604800;secure;HttpOnly"}, + {Http::Headers::get().Location.get(), + "https://traffic.example.com/original_path?var1=1&var2=2"}, + }; + + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), true)); + + filter_->onGetAccessTokenSuccess("access_code", "some-id-token", "some-refresh-token", + std::chrono::seconds(600)); + + EXPECT_EQ(scope_.counterFromString("test.my_prefix.oauth_failure").value(), 0); + EXPECT_EQ(scope_.counterFromString("test.my_prefix.oauth_success").value(), 1); +} + +TEST_F(OAuth2Test, SetBearerTokenWithEncryptionDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.oauth2_encrypt_tokens", "false"}}); + + init(getConfig(false /* forward_bearer_token */, true /* use_refresh_token */)); + + // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. + test_time_.setSystemTime(SystemTime(std::chrono::seconds(1000))); + + Http::TestRequestHeaderMapImpl request_headers{ + {Http::Headers::get().Path.get(), "/_oauth?code=123&state=" + TEST_ENCODED_STATE}, + {Http::Headers::get().Cookie.get(), + "OauthNonce=" + TEST_CSRF_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, + {Http::Headers::get().Cookie.get(), + "CodeVerifier=" + TEST_ENCRYPTED_CODE_VERIFIER + ";path=/;Max-Age=600;secure;HttpOnly"}, + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Scheme.get(), "https"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + }; + + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); + EXPECT_CALL(*oauth_client_, asyncGetAccessToken("123", TEST_CLIENT_ID, "asdf_client_secret_fdsa", "https://traffic.example.com" + TEST_CALLBACK, TEST_CODE_VERIFIER, AuthType::UrlEncodedBody)); @@ -1506,9 +1572,10 @@ TEST_F(OAuth2Test, CookieValidatorWithCookieDomain) { {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, {Http::Headers::get().Cookie.get(), fmt::format("{}={}", cookie_names.oauth_expires_, expires_at_s)}, - {Http::Headers::get().Cookie.get(), absl::StrCat(cookie_names.bearer_token_, "=xyztoken")}, {Http::Headers::get().Cookie.get(), - absl::StrCat(cookie_names.oauth_hmac_, "=zgWoFFmB6rbPHQQYQj35H+Fz+GYZgUrh/C48y0WHWRM=")}, + absl::StrCat(cookie_names.bearer_token_, "=", TEST_ENCRYPTED_ACCESS_TOKEN)}, + {Http::Headers::get().Cookie.get(), + absl::StrCat(cookie_names.oauth_hmac_, "=PHLtlCLTIjfuAocmHmW8QzM3YSTRF6L+E3o6a1+TiS4=")}, }; auto cookie_validator = @@ -1516,7 +1583,7 @@ TEST_F(OAuth2Test, CookieValidatorWithCookieDomain) { EXPECT_EQ(cookie_validator->token(), ""); EXPECT_EQ(cookie_validator->refreshToken(), ""); - cookie_validator->setParams(request_headers, "mock-secret"); + cookie_validator->setParams(request_headers, TEST_HMAC_SECRET); EXPECT_TRUE(cookie_validator->hmacIsValid()); EXPECT_TRUE(cookie_validator->timestampIsValid()); @@ -1537,14 +1604,15 @@ TEST_F(OAuth2Test, CookieValidatorSame) { {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, {Http::Headers::get().Cookie.get(), fmt::format("{}={}", cookie_names.oauth_expires_, expires_at_s)}, - {Http::Headers::get().Cookie.get(), absl::StrCat(cookie_names.bearer_token_, "=xyztoken")}, {Http::Headers::get().Cookie.get(), - absl::StrCat(cookie_names.oauth_hmac_, "=MSq8mkNQGdXx2LKGlLHMwSIj8rLZRnrHE6EWvvTUFx0=")}, + absl::StrCat(cookie_names.bearer_token_, "=", TEST_ENCRYPTED_ACCESS_TOKEN)}, + {Http::Headers::get().Cookie.get(), + absl::StrCat(cookie_names.oauth_hmac_, "=eYef0itomg0CAjYygAfCLwmS2s1DaiL+N1Ql5V48o4o=")}, }; auto cookie_validator = std::make_shared(test_time_, cookie_names, ""); EXPECT_EQ(cookie_validator->token(), ""); - cookie_validator->setParams(request_headers, "mock-secret"); + cookie_validator->setParams(request_headers, TEST_HMAC_SECRET); EXPECT_TRUE(cookie_validator->hmacIsValid()); EXPECT_TRUE(cookie_validator->timestampIsValid()); @@ -1567,12 +1635,13 @@ TEST_F(OAuth2Test, CookieValidatorSame) { {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, {Http::Headers::get().Cookie.get(), fmt::format("{}={}", cookie_names.oauth_expires_, new_expires_at_s)}, - {Http::Headers::get().Cookie.get(), absl::StrCat(cookie_names.bearer_token_, "=xyztoken")}, {Http::Headers::get().Cookie.get(), - absl::StrCat(cookie_names.oauth_hmac_, "=dbl04CSr6eWF52wdNDCRt/Uw6A4y41wbpmtUWRyD2Fo=")}, + absl::StrCat(cookie_names.bearer_token_, "=", TEST_ENCRYPTED_ACCESS_TOKEN)}, + {Http::Headers::get().Cookie.get(), + absl::StrCat(cookie_names.oauth_hmac_, "=VSTrKslW8ZNUqwgP+6Ocm1+7+NcF8GG/e1dqKsq14rc=")}, }; - cookie_validator->setParams(request_headers_second, "mock-secret"); + cookie_validator->setParams(request_headers_second, TEST_HMAC_SECRET); EXPECT_TRUE(cookie_validator->hmacIsValid()); EXPECT_TRUE(cookie_validator->timestampIsValid()); @@ -1592,9 +1661,9 @@ TEST_F(OAuth2Test, CookieValidatorInvalidExpiresAt) { {Http::Headers::get().Path.get(), "/anypath"}, {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, {Http::Headers::get().Cookie.get(), "OauthExpires=notanumber"}, - {Http::Headers::get().Cookie.get(), "BearerToken=xyztoken"}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, {Http::Headers::get().Cookie.get(), "OauthHMAC=" - "c+1qzyrMmqG8+O4dn7b28OvNNDWcb04yJfNbZCE1zYE="}, + "042KfjoL8OTsm8r4l6IO5dlxjzkaTDSyCaAibGI00bM="}, }; auto cookie_validator = std::make_shared( @@ -1602,7 +1671,7 @@ TEST_F(OAuth2Test, CookieValidatorInvalidExpiresAt) { CookieNames{"BearerToken", "OauthHMAC", "OauthExpires", "IdToken", "RefreshToken", "OauthNonce", "CodeVerifier"}, ""); - cookie_validator->setParams(request_headers, "mock-secret"); + cookie_validator->setParams(request_headers, TEST_HMAC_SECRET); EXPECT_TRUE(cookie_validator->hmacIsValid()); EXPECT_FALSE(cookie_validator->timestampIsValid()); @@ -1631,9 +1700,11 @@ TEST_F(OAuth2Test, CookieValidatorCanUpdateToken) { // Verify that we 401 the request if the state query param doesn't contain a valid URL. TEST_F(OAuth2Test, OAuthTestInvalidUrlInStateQueryParam) { - // {"url":"blah","csrf_token":"${extracted}"} + test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); + static const std::string state_with_invalid_url = - "eyJ1cmwiOiJibGFoIiwiY3NyZl90b2tlbiI6IjAwMDAwMDAwMDc1YmNkMTUifQ"; + "eyJ1cmwiOiJibGFoIiwiY3NyZl90b2tlbiI6IjAwMDAwMDAwMDc1YmNkMTUubmE2a3J1NHgxcEhnb2NTSWVVL21kdEhZ" + "bjU4R2gxYnF3ZVM0WFhvaXFWZz0ifQ"; Http::TestRequestHeaderMapImpl request_headers{ {Http::Headers::get().Host.get(), "traffic.example.com"}, {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, @@ -1641,7 +1712,7 @@ TEST_F(OAuth2Test, OAuthTestInvalidUrlInStateQueryParam) { "/_oauth?code=abcdefxyz123&scope=" + TEST_ENCODED_AUTH_SCOPES + "&state=" + state_with_invalid_url}, {Http::Headers::get().Cookie.get(), "OauthExpires=123"}, - {Http::Headers::get().Cookie.get(), "BearerToken=legit_token"}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, {Http::Headers::get().Cookie.get(), "OauthHMAC=" "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMj" @@ -1660,7 +1731,7 @@ TEST_F(OAuth2Test, OAuthTestInvalidUrlInStateQueryParam) { EXPECT_CALL(*validator_, setParams(_, _)); EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); - std::string legit_token{"legit_token"}; + std::string legit_token{"access_code"}; EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), false)); @@ -1683,7 +1754,7 @@ TEST_F(OAuth2Test, OAuthTestCallbackUrlInStateQueryParam) { "&state=" + state_with_callback_url}, {Http::Headers::get().Cookie.get(), "OauthExpires=123"}, - {Http::Headers::get().Cookie.get(), "BearerToken=legit_token"}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, {Http::Headers::get().Cookie.get(), "OauthHMAC=" "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMj" @@ -1701,31 +1772,13 @@ TEST_F(OAuth2Test, OAuthTestCallbackUrlInStateQueryParam) { EXPECT_CALL(*validator_, setParams(_, _)); EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); - std::string legit_token{"legit_token"}; + std::string legit_token{"access_code"}; EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_response_headers), false)); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers, false)); - - Http::TestRequestHeaderMapImpl final_request_headers{ - {Http::Headers::get().Host.get(), "traffic.example.com"}, - {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, - {Http::Headers::get().Path.get(), - "/_oauth?code=abcdefxyz123&scope=" + TEST_ENCODED_AUTH_SCOPES + - "&state=" + state_with_callback_url}, - {Http::Headers::get().Cookie.get(), "OauthExpires=123"}, - {Http::Headers::get().Cookie.get(), "BearerToken=legit_token"}, - {Http::Headers::get().Cookie.get(), - "OauthHMAC=" - "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMj" - "RlNjMxZTJmNTZkYzRmZTM0ZQ===="}, - {Http::Headers::get().Cookie.get(), "OauthNonce=" + TEST_CSRF_TOKEN}, - {Http::CustomHeaders::get().Authorization.get(), "Bearer legit_token"}, - }; - - EXPECT_EQ(request_headers, final_request_headers); } TEST_F(OAuth2Test, OAuthTestUpdatePathAfterSuccess) { @@ -1738,7 +1791,7 @@ TEST_F(OAuth2Test, OAuthTestUpdatePathAfterSuccess) { "/_oauth?code=abcdefxyz123&scope=" + TEST_ENCODED_AUTH_SCOPES + "&state=" + TEST_ENCODED_STATE}, {Http::Headers::get().Cookie.get(), "OauthExpires=123"}, - {Http::Headers::get().Cookie.get(), "BearerToken=legit_token"}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, {Http::Headers::get().Cookie.get(), "OauthHMAC=" "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMj" @@ -1756,7 +1809,7 @@ TEST_F(OAuth2Test, OAuthTestUpdatePathAfterSuccess) { EXPECT_CALL(*validator_, setParams(_, _)); EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); - std::string legit_token{"legit_token"}; + std::string legit_token{"access_code"}; EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); EXPECT_CALL(decoder_callbacks_, @@ -1764,23 +1817,21 @@ TEST_F(OAuth2Test, OAuthTestUpdatePathAfterSuccess) { EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers, false)); - Http::TestRequestHeaderMapImpl final_request_headers{ - {Http::Headers::get().Host.get(), "traffic.example.com"}, - {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, - {Http::Headers::get().Path.get(), - "/_oauth?code=abcdefxyz123&scope=" + TEST_ENCODED_AUTH_SCOPES + - "&state=" + TEST_ENCODED_STATE}, - {Http::Headers::get().Cookie.get(), "OauthExpires=123"}, - {Http::Headers::get().Cookie.get(), "BearerToken=legit_token"}, - {Http::Headers::get().Cookie.get(), - "OauthHMAC=" - "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMj" - "RlNjMxZTJmNTZkYzRmZTM0ZQ===="}, - {Http::Headers::get().Cookie.get(), "OauthNonce=" + TEST_CSRF_TOKEN}, - {Http::CustomHeaders::get().Authorization.get(), "Bearer legit_token"}, - }; + EXPECT_EQ(request_headers.getHostValue(), "traffic.example.com"); + EXPECT_EQ(request_headers.getMethodValue(), Http::Headers::get().MethodValues.Get); + EXPECT_EQ(request_headers.getPathValue(), + "/_oauth?code=abcdefxyz123&scope=" + TEST_ENCODED_AUTH_SCOPES + + "&state=" + TEST_ENCODED_STATE); + auto auth_header = request_headers.get(Http::CustomHeaders::get().Authorization); + EXPECT_EQ(auth_header[0]->value().getStringView(), "Bearer access_code"); - EXPECT_EQ(request_headers, final_request_headers); + auto cookies = Http::Utility::parseCookies(request_headers); + EXPECT_EQ(cookies["OauthExpires"], "123"); + EXPECT_EQ(cookies["BearerToken"], "access_code"); + EXPECT_EQ( + cookies["OauthHMAC"], + "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMjRlNjMxZTJmNTZkYzRmZTM0ZQ===="); + EXPECT_EQ(cookies["OauthNonce"], TEST_CSRF_TOKEN); } /** @@ -1865,23 +1916,26 @@ TEST_F(OAuth2Test, OAuthTestFullFlowPostWithCookieDomain) { // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); const std::chrono::seconds expiredTime(10); - filter_->updateTokens("accessToken", "idToken", "refreshToken", expiredTime); + filter_->updateTokens("access_code", "some-id-token", "some-refresh-token", expiredTime); // Expected response after the callback & validation is complete - verifying we kept the // state and method of the original request, including the query string parameters. Http::TestRequestHeaderMapImpl second_response_headers{ {Http::Headers::get().Status.get(), "302"}, {Http::Headers::get().SetCookie.get(), - "OauthHMAC=vU9fV//fsKp9ARyrz/HZx2CqWFCmUygihdl18qR5u78=;" + "OauthHMAC=seD1HFQMr2pDwXgZKYQ1+D8R/p8tCa2fO8xTmfAgAUg=;" "domain=example.com;path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), "OauthExpires=10;domain=example.com;path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=accessToken;domain=example.com;path=/;Max-Age=10;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + + ";domain=example.com;path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=idToken;domain=example.com;path=/;Max-Age=10;secure;HttpOnly"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + + ";domain=example.com;path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=refreshToken;domain=example.com;path=/;Max-Age=604800;secure;HttpOnly"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + + ";domain=example.com;path=/;Max-Age=604800;secure;HttpOnly"}, {Http::Headers::get().Location.get(), "https://traffic.example.com/original_path?var1=1&var2=2"}, }; @@ -1979,21 +2033,22 @@ TEST_F(OAuth2Test, OAuthTestFullFlowPostWithSpecialCharactersForJson) { // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); const std::chrono::seconds expiredTime(10); - filter_->updateTokens("accessToken", "idToken", "refreshToken", expiredTime); + filter_->updateTokens("access_code", "some-id-token", "some-refresh-token", expiredTime); // Expected response after the callback & validation is complete - verifying we kept the // state and method of the original request, including the query string parameters. Http::TestRequestHeaderMapImpl second_response_headers{ {Http::Headers::get().Status.get(), "302"}, {Http::Headers::get().SetCookie.get(), "OauthHMAC=" - "OYnODPsSGabEpZ2LAiPxyjAFgN/7/5Xg24G7jUoUbyI=;" + "UzbL/bzvWEP8oaoPDfQrD0zu6zC6m0yBOowKx1Mdr6o=;" "path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), "OauthExpires=10;path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=accessToken;path=/;Max-Age=10;secure;HttpOnly"}, - {Http::Headers::get().SetCookie.get(), "IdToken=idToken;path=/;Max-Age=10;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=refreshToken;path=/;Max-Age=604800;secure;HttpOnly"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=10;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + ";path=/;Max-Age=604800;secure;HttpOnly"}, {Http::Headers::get().Location.get(), "https://traffic.example.com" + url_with_special_characters}, }; @@ -2024,9 +2079,9 @@ class DisabledIdTokenTests : public OAuth2Test { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=" + access_code_ + ";path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=" + refresh_token_ + ";path=/;Max-Age=600;secure;HttpOnly"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, }; init(getConfig(true /* forward_bearer_token */, true /* use_refresh_token */, @@ -2080,8 +2135,8 @@ TEST_F(DisabledIdTokenTests, SetCookieIgnoresIdTokenWhenDisabledRefreshToken) { EXPECT_EQ(cookies[cookie_names.oauth_hmac_], hmac_without_id_token_); EXPECT_EQ(cookies[cookie_names.oauth_expires_], "1600"); // Uses default_refresh_token_expires_in since not a legitimate JWT. - EXPECT_EQ(cookies[cookie_names.bearer_token_], access_code_); - EXPECT_EQ(cookies[cookie_names.refresh_token_], refresh_token_); + EXPECT_EQ(cookies[cookie_names.bearer_token_], "access_code"); + EXPECT_EQ(cookies[cookie_names.refresh_token_], "some-refresh-token"); EXPECT_EQ(cookies.contains(cookie_names.id_token_), false); // And ensure when the response comes back, it has the same cookies in the `expected_headers_`. @@ -2202,9 +2257,9 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokens) { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().Location.get(), ""}, }; @@ -2236,11 +2291,11 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensUseRefreshToken) { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=some-refresh-token;path=/;Max-Age=604800;secure;HttpOnly"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + ";path=/;Max-Age=604800;secure;HttpOnly"}, {Http::Headers::get().Location.get(), ""}, }; @@ -2276,11 +2331,11 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensUseRefreshTokenAndDefaultRefr {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=some-refresh-token;path=/;Max-Age=1200;secure;HttpOnly"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + ";path=/;Max-Age=1200;secure;HttpOnly"}, {Http::Headers::get().Location.get(), ""}, }; @@ -2319,6 +2374,12 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensUseRefreshTokenAndRefreshToke "eyJ1bmlxdWVfbmFtZSI6ImFsZXhjZWk4OCIsInN1YiI6ImFsZXhjZWk4OCIsImp0aSI6IjQ5ZTFjMzc1IiwiYXVkIjoi" "dGVzdCIsIm5iZiI6MTcwNzQxNDYzNSwiZXhwIjoyNTU0NDE2MDAwLCJpYXQiOjE3MDc0MTQ2MzYsImlzcyI6ImRvdG5l" "dC11c2VyLWp3dHMifQ.LaGOw6x0-m7r-WzxgCIdPnAfp0O1hy6mW4klq9Vs2XM"; + const std::string encrypted_refresh_token = + "Fc1bBwAAAAAVzVsHAAAAANmnPnluIb9exn3WlbkgaDHNTVoZUE-1O8H_" + "amXtsHZWG04QXuzJxsFxxe58HpCeWYx7QYi886mP3fCWDBrOJZ4DkwJjQXtvp9VdmKhCr1qCYQ9mSdv6GY50g-aOOr-" + "x1wXNGCfnURYA48u2BulYuHqG2FzNAfbPo8uNO0IS3CUNE3C9gLcs4gHq9AjMwXVe3PLxV0ihrcXCUVp0ao9R2k2Ki1V" + "LZpaH6ntay0IUJft2hjvq3lVvtCakEH0LYmzx9G0MGwaqiaeeFBNQyCY9iji5BOAfFezKnLKAvsYn2egVDHEFXCCSUW2" + "3YEA57eGNDrs1PIZXRvLrjyJCiBE-0Iiq74MgHSG6usBK21wks8VOGyIy3qRkz-LcmgLX9ZB1lA"; // Expected response after the callback is complete. Http::TestRequestHeaderMapImpl expected_headers{ @@ -2328,11 +2389,11 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensUseRefreshTokenAndRefreshToke {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=" + refreshToken + ";path=/;Max-Age=2554415000;secure;HttpOnly"}, + "RefreshToken=" + encrypted_refresh_token + ";path=/;Max-Age=2554415000;secure;HttpOnly"}, {Http::Headers::get().Location.get(), ""}, }; @@ -2371,6 +2432,12 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensUseRefreshTokenAndExpiredRefr "eyJ1bmlxdWVfbmFtZSI6ImFsZXhjZWk4OCIsInN1YiI6ImFsZXhjZWk4OCIsImp0aSI6IjQ5ZTFjMzc1IiwiYXVkIjoi" "dGVzdCIsIm5iZiI6MTcwNzQxNDYzNSwiZXhwIjoyNTU0NDE2MDAwLCJpYXQiOjE3MDc0MTQ2MzYsImlzcyI6ImRvdG5l" "dC11c2VyLWp3dHMifQ.LaGOw6x0-m7r-WzxgCIdPnAfp0O1hy6mW4klq9Vs2XM"; + const std::string encrypted_refresh_token = + "Fc1bBwAAAAAVzVsHAAAAANmnPnluIb9exn3WlbkgaDHNTVoZUE-1O8H_" + "amXtsHZWG04QXuzJxsFxxe58HpCeWYx7QYi886mP3fCWDBrOJZ4DkwJjQXtvp9VdmKhCr1qCYQ9mSdv6GY50g-aOOr-" + "x1wXNGCfnURYA48u2BulYuHqG2FzNAfbPo8uNO0IS3CUNE3C9gLcs4gHq9AjMwXVe3PLxV0ihrcXCUVp0ao9R2k2Ki1V" + "LZpaH6ntay0IUJft2hjvq3lVvtCakEH0LYmzx9G0MGwaqiaeeFBNQyCY9iji5BOAfFezKnLKAvsYn2egVDHEFXCCSUW2" + "3YEA57eGNDrs1PIZXRvLrjyJCiBE-0Iiq74MgHSG6usBK21wks8VOGyIy3qRkz-LcmgLX9ZB1lA"; // Expected response after the callback is complete. Http::TestRequestHeaderMapImpl expected_headers{ @@ -2380,11 +2447,11 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensUseRefreshTokenAndExpiredRefr {Http::Headers::get().SetCookie.get(), "OauthExpires=2554515600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=" + refreshToken + ";path=/;Max-Age=0;secure;HttpOnly"}, + "RefreshToken=" + encrypted_refresh_token + ";path=/;Max-Age=0;secure;HttpOnly"}, {Http::Headers::get().Location.get(), ""}, }; @@ -2422,6 +2489,11 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensUseRefreshTokenAndNoExpClaimI "eyJhbGciOiJIUzI1NiJ9." "eyJSb2xlIjoiQWRtaW4iLCJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkphdmFJblVzZSIsImlhdCI6MTcwODA2" "NDcyOH0.92H-X2Oa4ECNmFLZBWBHP0BJyEHDprLkEIc2JBJYwkI"; + const std::string encrypted_refresh_token = + "Fc1bBwAAAAAVzVsHAAAAANmnPnluIb9exn3WlbkgaDE7Qej3gaQyBPqvzoNiSVn8-sv2lmZF7nT3OVnBe7X-KK-" + "jOOVaiHesGNEsPt5F0CmkMytmf-t0VMASmnC8FhgnCsRkf2XHL_" + "z18YGJTvbHgc6QDdKUDwGuMTL048BdQYelXZ9nwtNchSkbZIa8yUf5wrZtEvFpOzE-brHaI3LOWmHaQ27h_" + "lm5eH0qKwMy_jXZMXhxzO_-Rrz9XBlVwIMP"; // Expected response after the callback is complete. Http::TestRequestHeaderMapImpl expected_headers{ @@ -2431,11 +2503,11 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensUseRefreshTokenAndNoExpClaimI {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=" + refreshToken + ";path=/;Max-Age=1200;secure;HttpOnly"}, + "RefreshToken=" + encrypted_refresh_token + ";path=/;Max-Age=1200;secure;HttpOnly"}, {Http::Headers::get().Location.get(), ""}, }; @@ -2458,7 +2530,7 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensIdTokenExpiresInFromJwt) { OAuth2Config_AuthType_URL_ENCODED_BODY /* encoded_body_type */, 1200 /* default_refresh_token_expires_in */)); TestScopedRuntime scoped_runtime; - oauthHMAC = "UjDfDiq1RHQooE16EhoadVxwOD7sBvrn+S8CZ2k4tvM=;"; + oauthHMAC = "MqrMKGLbdIEogLWZPRffaVTXDGRRveG3gn9bZu5Gd4Q=;"; // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. test_time_.setSystemTime(SystemTime(std::chrono::seconds(1000))); @@ -2475,6 +2547,12 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensIdTokenExpiresInFromJwt) { "eyJ1bmlxdWVfbmFtZSI6ImFsZXhjZWk4OCIsInN1YiI6ImFsZXhjZWk4OCIsImp0aSI6IjQ5ZTFjMzc1IiwiYXVkIjoi" "dGVzdCIsIm5iZiI6MTcwNzQxNDYzNSwiZXhwIjoyNTU0NDE2MDAwLCJpYXQiOjE3MDc0MTQ2MzYsImlzcyI6ImRvdG5l" "dC11c2VyLWp3dHMifQ.LaGOw6x0-m7r-WzxgCIdPnAfp0O1hy6mW4klq9Vs2XM"; + const std::string encrypted_id_token = + "Fc1bBwAAAAAVzVsHAAAAANmnPnluIb9exn3WlbkgaDHNTVoZUE-1O8H_" + "amXtsHZWG04QXuzJxsFxxe58HpCeWYx7QYi886mP3fCWDBrOJZ4DkwJjQXtvp9VdmKhCr1qCYQ9mSdv6GY50g-aOOr-" + "x1wXNGCfnURYA48u2BulYuHqG2FzNAfbPo8uNO0IS3CUNE3C9gLcs4gHq9AjMwXVe3PLxV0ihrcXCUVp0ao9R2k2Ki1V" + "LZpaH6ntay0IUJft2hjvq3lVvtCakEH0LYmzx9G0MGwaqiaeeFBNQyCY9iji5BOAfFezKnLKAvsYn2egVDHEFXCCSUW2" + "3YEA57eGNDrs1PIZXRvLrjyJCiBE-0Iiq74MgHSG6usBK21wks8VOGyIy3qRkz-LcmgLX9ZB1lA"; // Expected response after the callback is complete. Http::TestRequestHeaderMapImpl expected_headers{ @@ -2484,17 +2562,17 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensIdTokenExpiresInFromJwt) { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=" + id_token + ";path=/;Max-Age=2554415000;secure;HttpOnly"}, + "IdToken=" + encrypted_id_token + ";path=/;Max-Age=2554415000;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=refresh-token;path=/;Max-Age=1200;secure;HttpOnly"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + ";path=/;Max-Age=1200;secure;HttpOnly"}, {Http::Headers::get().Location.get(), ""}, }; EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), true)); - filter_->onGetAccessTokenSuccess("access_code", id_token, "refresh-token", + filter_->onGetAccessTokenSuccess("access_code", id_token, "some-refresh-token", std::chrono::seconds(600)); } @@ -2510,7 +2588,7 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensExpiredIdToken) { OAuth2Config_AuthType_URL_ENCODED_BODY /* encoded_body_type */, 1200 /* default_refresh_token_expires_in */)); TestScopedRuntime scoped_runtime; - oauthHMAC = "HSyUburg3d4IXM2+5gCiIEn6VvLm584MqFmVEed4Jyc=;"; + oauthHMAC = "eQmiVNw3uAZixmzqtd75kD/0MeSJzS/ROl99NNfWoyU=;"; // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. test_time_.setSystemTime(SystemTime(std::chrono::seconds(2554515000))); @@ -2527,6 +2605,12 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensExpiredIdToken) { "eyJ1bmlxdWVfbmFtZSI6ImFsZXhjZWk4OCIsInN1YiI6ImFsZXhjZWk4OCIsImp0aSI6IjQ5ZTFjMzc1IiwiYXVkIjoi" "dGVzdCIsIm5iZiI6MTcwNzQxNDYzNSwiZXhwIjoyNTU0NDE2MDAwLCJpYXQiOjE3MDc0MTQ2MzYsImlzcyI6ImRvdG5l" "dC11c2VyLWp3dHMifQ.LaGOw6x0-m7r-WzxgCIdPnAfp0O1hy6mW4klq9Vs2XM"; + const std::string encrypted_id_token = + "Fc1bBwAAAAAVzVsHAAAAANmnPnluIb9exn3WlbkgaDHNTVoZUE-1O8H_" + "amXtsHZWG04QXuzJxsFxxe58HpCeWYx7QYi886mP3fCWDBrOJZ4DkwJjQXtvp9VdmKhCr1qCYQ9mSdv6GY50g-aOOr-" + "x1wXNGCfnURYA48u2BulYuHqG2FzNAfbPo8uNO0IS3CUNE3C9gLcs4gHq9AjMwXVe3PLxV0ihrcXCUVp0ao9R2k2Ki1V" + "LZpaH6ntay0IUJft2hjvq3lVvtCakEH0LYmzx9G0MGwaqiaeeFBNQyCY9iji5BOAfFezKnLKAvsYn2egVDHEFXCCSUW2" + "3YEA57eGNDrs1PIZXRvLrjyJCiBE-0Iiq74MgHSG6usBK21wks8VOGyIy3qRkz-LcmgLX9ZB1lA"; // Expected response after the callback is complete. Http::TestRequestHeaderMapImpl expected_headers{ @@ -2536,17 +2620,17 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensExpiredIdToken) { {Http::Headers::get().SetCookie.get(), "OauthExpires=2554515600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=" + id_token + ";path=/;Max-Age=0;secure;HttpOnly"}, + "IdToken=" + encrypted_id_token + ";path=/;Max-Age=0;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=refresh-token;path=/;Max-Age=1200;secure;HttpOnly"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + ";path=/;Max-Age=1200;secure;HttpOnly"}, {Http::Headers::get().Location.get(), ""}, }; EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), true)); - filter_->onGetAccessTokenSuccess("access_code", id_token, "refresh-token", + filter_->onGetAccessTokenSuccess("access_code", id_token, "some-refresh-token", std::chrono::seconds(600)); } @@ -2564,7 +2648,7 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensNoExpClaimInIdToken) { OAuth2Config_AuthType_URL_ENCODED_BODY /* encoded_body_type */, 1200 /* default_refresh_token_expires_in */)); TestScopedRuntime scoped_runtime; - oauthHMAC = "6CyS8TiamKlAVtPpHANqYOwS59gOTCIRXV9j1GtGwqA=;"; + oauthHMAC = "CU0eIzpTJSD/LFOVPaH7ypOQqqBvh4s6Tin3ip9rajk=;"; // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. test_time_.setSystemTime(SystemTime(std::chrono::seconds(1000))); @@ -2580,6 +2664,11 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensNoExpClaimInIdToken) { "eyJhbGciOiJIUzI1NiJ9." "eyJSb2xlIjoiQWRtaW4iLCJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkphdmFJblVzZSIsImlhdCI6MTcwODA2" "NDcyOH0.92H-X2Oa4ECNmFLZBWBHP0BJyEHDprLkEIc2JBJYwkI"; + const std::string encrypted_id_token = + "Fc1bBwAAAAAVzVsHAAAAANmnPnluIb9exn3WlbkgaDE7Qej3gaQyBPqvzoNiSVn8-sv2lmZF7nT3OVnBe7X-KK-" + "jOOVaiHesGNEsPt5F0CmkMytmf-t0VMASmnC8FhgnCsRkf2XHL_" + "z18YGJTvbHgc6QDdKUDwGuMTL048BdQYelXZ9nwtNchSkbZIa8yUf5wrZtEvFpOzE-brHaI3LOWmHaQ27h_" + "lm5eH0qKwMy_jXZMXhxzO_-Rrz9XBlVwIMP"; // Expected response after the callback is complete. Http::TestRequestHeaderMapImpl expected_headers{ @@ -2589,17 +2678,17 @@ TEST_F(OAuth2Test, OAuthAccessTokenSucessWithTokensNoExpClaimInIdToken) { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "IdToken=" + id_token + ";path=/;Max-Age=600;secure;HttpOnly"}, + "IdToken=" + encrypted_id_token + ";path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=refresh-token;path=/;Max-Age=1200;secure;HttpOnly"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + ";path=/;Max-Age=1200;secure;HttpOnly"}, {Http::Headers::get().Location.get(), ""}, }; EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), true)); - filter_->onGetAccessTokenSuccess("access_code", id_token, "refresh-token", + filter_->onGetAccessTokenSuccess("access_code", id_token, "some-refresh-token", std::chrono::seconds(600)); } @@ -2642,11 +2731,10 @@ TEST_F(OAuth2Test, CookieValidatorInTransition) { {Http::Headers::get().Path.get(), "/_signout"}, {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, {Http::Headers::get().Cookie.get(), "OauthExpires=1600"}, - {Http::Headers::get().Cookie.get(), "BearerToken=access_code"}, - {Http::Headers::get().Cookie.get(), "IdToken=some-id-token"}, - {Http::Headers::get().Cookie.get(), "RefreshToken=some-refresh-token"}, - {Http::Headers::get().Cookie.get(), "OauthHMAC=" - "Y9gCpVnhyaY+ecSxt/ZLZc/OMb8ZNivrVH1RByJxEbs="}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, + {Http::Headers::get().Cookie.get(), "IdToken=" + TEST_ENCRYPTED_ID_TOKEN}, + {Http::Headers::get().Cookie.get(), "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN}, + {Http::Headers::get().Cookie.get(), "OauthHMAC=eK7Kw2VqlnZJiz93KTnZqUar3ajNAe+ubmosGFkyL4I="}, }; auto cookie_validator = std::make_shared( @@ -2662,12 +2750,10 @@ TEST_F(OAuth2Test, CookieValidatorInTransition) { {Http::Headers::get().Path.get(), "/_signout"}, {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, {Http::Headers::get().Cookie.get(), "OauthExpires=1600"}, - {Http::Headers::get().Cookie.get(), "BearerToken=access_code"}, - {Http::Headers::get().Cookie.get(), "IdToken=some-id-token"}, - {Http::Headers::get().Cookie.get(), "RefreshToken=some-refresh-token"}, - {Http::Headers::get().Cookie.get(), - "OauthHMAC=" - "NjNkODAyYTU1OWUxYzlhNjNlNzljNGIxYjdmNjRiNjVjZmNlMzFiZjE5MzYyYmViNTQ3ZDUxMDcyMjcxMTFiYg=="}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, + {Http::Headers::get().Cookie.get(), "IdToken=" + TEST_ENCRYPTED_ID_TOKEN}, + {Http::Headers::get().Cookie.get(), "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN}, + {Http::Headers::get().Cookie.get(), "OauthHMAC=eK7Kw2VqlnZJiz93KTnZqUar3ajNAe+ubmosGFkyL4I="}, }; cookie_validator->setParams(request_headers_hexbase64, "mock-secret"); @@ -2971,7 +3057,7 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessToken) { {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, {Http::Headers::get().Scheme.get(), "https"}, {Http::Headers::get().Cookie.get(), fmt::format("OauthExpires={}", expires_at_s)}, - {Http::Headers::get().Cookie.get(), "BearerToken=xyztoken"}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, {Http::Headers::get().Cookie.get(), "OauthHMAC=dCu0otMcLoaGF73jrT+R8rGA0pnWyMgNf4+GivGrHEI="}, }; @@ -2994,7 +3080,7 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessToken) { // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); const std::chrono::seconds expiredTime(10); - filter_->updateTokens("accessToken", "idToken", "refreshToken", expiredTime); + filter_->updateTokens("access_code", "some-id-token", "some-refresh-token", expiredTime); filter_->finishRefreshAccessTokenFlow(); @@ -3004,24 +3090,25 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessToken) { Http::TestResponseHeaderMapImpl expected_response_headers{ {Http::Headers::get().SetCookie.get(), "OauthHMAC=" - "OYnODPsSGabEpZ2LAiPxyjAFgN/7/5Xg24G7jUoUbyI=;" + "UzbL/bzvWEP8oaoPDfQrD0zu6zC6m0yBOowKx1Mdr6o=;" "path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), "OauthExpires=10;path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=accessToken;path=/;Max-Age=10;secure;HttpOnly"}, - {Http::Headers::get().SetCookie.get(), "IdToken=idToken;path=/;Max-Age=10;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=refreshToken;path=/;Max-Age=604800;secure;HttpOnly"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=10;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + ";path=/;Max-Age=604800;secure;HttpOnly"}, }; EXPECT_THAT(response_headers, HeaderMapEqualRef(&expected_response_headers)); auto cookies = Http::Utility::parseCookies(request_headers); - EXPECT_EQ(cookies.at("OauthHMAC"), "OYnODPsSGabEpZ2LAiPxyjAFgN/7/5Xg24G7jUoUbyI="); + EXPECT_EQ(cookies.at("OauthHMAC"), "UzbL/bzvWEP8oaoPDfQrD0zu6zC6m0yBOowKx1Mdr6o="); EXPECT_EQ(cookies.at("OauthExpires"), "10"); - EXPECT_EQ(cookies.at("BearerToken"), "accessToken"); - EXPECT_EQ(cookies.at("IdToken"), "idToken"); - EXPECT_EQ(cookies.at("RefreshToken"), "refreshToken"); + EXPECT_EQ(cookies.at("BearerToken"), "access_code"); + EXPECT_EQ(cookies.at("IdToken"), "some-id-token"); + EXPECT_EQ(cookies.at("RefreshToken"), "some-refresh-token"); } // When a refresh flow succeeds, but a new refresh token isn't received from the OAuth server, the @@ -3031,7 +3118,9 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessTokenNoNewRefreshToken) const auto expires_at_s = DateUtil::nowToSeconds(test_time_.timeSystem()) - 10; - std::string legit_refresh_token{"legit_refresh_token"}; + std::string legit_refresh_token = "legit_refresh_token"; + std::string encrypted_refresh_token = + "Fc1bBwAAAAAVzVsHAAAAAOh8bHz59OyZPtKMgiX5FWJMyTXqsPjbf1j-Ao8fn1tb"; // the third request to the oauth filter with URI parameters. Http::TestRequestHeaderMapImpl request_headers{ {Http::Headers::get().Path.get(), "/original_path?var1=1&var2=2"}, @@ -3039,8 +3128,8 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessTokenNoNewRefreshToken) {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, {Http::Headers::get().Scheme.get(), "https"}, {Http::Headers::get().Cookie.get(), fmt::format("OauthExpires={}", expires_at_s)}, - {Http::Headers::get().Cookie.get(), fmt::format("RefreshToken={}", legit_refresh_token)}, - {Http::Headers::get().Cookie.get(), "BearerToken=xyztoken"}, + {Http::Headers::get().Cookie.get(), fmt::format("RefreshToken={}", encrypted_refresh_token)}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, {Http::Headers::get().Cookie.get(), "OauthHMAC=dCu0otMcLoaGF73jrT+R8rGA0pnWyMgNf4+GivGrHEI="}, }; @@ -3062,7 +3151,7 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessTokenNoNewRefreshToken) // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); const std::chrono::seconds expiredTime(10); - filter_->updateTokens("accessToken", "idToken", "", expiredTime); + filter_->updateTokens("access_code", "some-id-token", "", expiredTime); filter_->finishRefreshAccessTokenFlow(); @@ -3072,24 +3161,26 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessTokenNoNewRefreshToken) Http::TestResponseHeaderMapImpl expected_response_headers{ {Http::Headers::get().SetCookie.get(), "OauthHMAC=" - "AWc2PEcPGGXlOtGGLOsT6rFnW9qxOVk0NvfBpZHRY3I=;" + "xQCNvPMLwq3rF1dB/mSwyVz7kcIZai8pD8rS5SNLgRU=;" "path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), "OauthExpires=10;path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=accessToken;path=/;Max-Age=10;secure;HttpOnly"}, - {Http::Headers::get().SetCookie.get(), "IdToken=idToken;path=/;Max-Age=10;secure;HttpOnly"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + ";path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - fmt::format("RefreshToken={};path=/;Max-Age=604800;secure;HttpOnly", legit_refresh_token)}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=10;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + fmt::format("RefreshToken={};path=/;Max-Age=604800;secure;HttpOnly", + encrypted_refresh_token)}, }; EXPECT_THAT(response_headers, HeaderMapEqualRef(&expected_response_headers)); auto cookies = Http::Utility::parseCookies(request_headers); - EXPECT_EQ(cookies.at("OauthHMAC"), "AWc2PEcPGGXlOtGGLOsT6rFnW9qxOVk0NvfBpZHRY3I="); + EXPECT_EQ(cookies.at("OauthHMAC"), "xQCNvPMLwq3rF1dB/mSwyVz7kcIZai8pD8rS5SNLgRU="); EXPECT_EQ(cookies.at("OauthExpires"), "10"); - EXPECT_EQ(cookies.at("BearerToken"), "accessToken"); - EXPECT_EQ(cookies.at("IdToken"), "idToken"); - EXPECT_EQ(cookies.at("RefreshToken"), legit_refresh_token); + EXPECT_EQ(cookies.at("BearerToken"), "access_code"); + EXPECT_EQ(cookies.at("IdToken"), "some-id-token"); + EXPECT_EQ(cookies.at("RefreshToken"), "legit_refresh_token"); } TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessTokenWithBasicAuth) { @@ -3098,6 +3189,8 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessTokenWithBasicAuth) { OAuth2Config_AuthType_BASIC_AUTH /* authType */)); + // 1. Test sending a request with expired tokens. + // Set the expiration time to 10 seconds in the past to simulate token expiration. const auto expires_at_s = DateUtil::nowToSeconds(test_time_.timeSystem()) - 10; Http::TestRequestHeaderMapImpl request_headers{ @@ -3106,31 +3199,44 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessTokenWithBasicAuth) { {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Post}, {Http::Headers::get().Scheme.get(), "https"}, {Http::Headers::get().Cookie.get(), fmt::format("OauthExpires={}", expires_at_s)}, - {Http::Headers::get().Cookie.get(), "BearerToken=xyztoken"}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, {Http::Headers::get().Cookie.get(), "OauthHMAC=dCu0otMcLoaGF73jrT+R8rGA0pnWyMgNf4+GivGrHEI="}, - {Http::Headers::get().Cookie.get(), "RefreshToken=legit_refresh_token"}, + {Http::Headers::get().Cookie.get(), "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN}, }; - std::string legit_refresh_token{"legit_refresh_token"}; + std::string legit_refresh_token{"some-refresh-token"}; EXPECT_CALL(*validator_, refreshToken()).WillRepeatedly(ReturnRef(legit_refresh_token)); EXPECT_CALL(*validator_, setParams(_, _)); EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false)); EXPECT_CALL(*validator_, canUpdateTokenByRefreshToken()).WillOnce(Return(true)); + // Filter should refresh the tokens using the refresh token because the tokens are expired and a + // refresh token is available. EXPECT_CALL(*oauth_client_, asyncRefreshAccessToken(legit_refresh_token, TEST_CLIENT_ID, "asdf_client_secret_fdsa", AuthType::BasicAuth)); + // Filter should stop iteration because the tokens are expired. EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter_->decodeHeaders(request_headers, false)); + // 2. Test refresh flow succeeds. + // The new tokens received from the refresh flow. + const std::string access_token = "accessToken"; + const std::string id_token = "idToken"; + const std::string refresh_token = "refreshToken"; + const std::string encrypted_id_token = "Fc1bBwAAAAAVzVsHAAAAAPD4z8oLeVyvkfTcl_cw198"; + const std::string encrypted_access_token = "Fc1bBwAAAAAVzVsHAAAAAGUINzc06x19yQYjN4Kb-YA"; + const std::string encrypted_refresh_token = "Fc1bBwAAAAAVzVsHAAAAACWUO4LpH2VJBN_6jSUWDPg"; + + // Filter should continue decoding because the tokens are refreshed. EXPECT_CALL(decoder_callbacks_, continueDecoding()); // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); const std::chrono::seconds expiredTime(10); - filter_->updateTokens("accessToken", "idToken", "refreshToken", expiredTime); + filter_->updateTokens(access_token, id_token, refresh_token, expiredTime); filter_->finishRefreshAccessTokenFlow(); @@ -3144,14 +3250,17 @@ TEST_F(OAuth2Test, OAuthTestSetCookiesAfterRefreshAccessTokenWithBasicAuth) { "path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), "OauthExpires=10;path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=accessToken;path=/;Max-Age=10;secure;HttpOnly"}, - {Http::Headers::get().SetCookie.get(), "IdToken=idToken;path=/;Max-Age=10;secure;HttpOnly"}, + "BearerToken=" + encrypted_access_token + ";path=/;Max-Age=10;secure;HttpOnly"}, + {Http::Headers::get().SetCookie.get(), + "IdToken=" + encrypted_id_token + ";path=/;Max-Age=10;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=refreshToken;path=/;Max-Age=604800;secure;HttpOnly"}, + "RefreshToken=" + encrypted_refresh_token + ";path=/;Max-Age=604800;secure;HttpOnly"}, }; + // Test the response headers are set correctly with the new tokens. EXPECT_THAT(response_headers, HeaderMapEqualRef(&expected_response_headers)); + // Test the request headers are updated with the new tokens. auto cookies = Http::Utility::parseCookies(request_headers); EXPECT_EQ(cookies.at("OauthHMAC"), "OYnODPsSGabEpZ2LAiPxyjAFgN/7/5Xg24G7jUoUbyI="); EXPECT_EQ(cookies.at("OauthExpires"), "10"); @@ -3186,11 +3295,14 @@ TEST_F(OAuth2Test, AllCookiesStrictSameSite) { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=some-refresh-token;path=/;Max-Age=604800;secure;HttpOnly;SameSite=Strict"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + + ";path=/;Max-Age=604800;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().Location.get(), ""}, }; @@ -3227,11 +3339,13 @@ TEST_F(OAuth2Test, AllCookiesNoneSameSite) { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly;SameSite=None"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly;SameSite=None"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=None"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly;SameSite=None"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=None"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=some-refresh-token;path=/;Max-Age=604800;secure;HttpOnly;SameSite=None"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + + ";path=/;Max-Age=604800;secure;HttpOnly;SameSite=None"}, {Http::Headers::get().Location.get(), ""}, }; @@ -3268,11 +3382,13 @@ TEST_F(OAuth2Test, AllCookiesLaxSameSite) { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly;SameSite=Lax"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly;SameSite=Lax"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Lax"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly;SameSite=Lax"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Lax"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=some-refresh-token;path=/;Max-Age=604800;secure;HttpOnly;SameSite=Lax"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + + ";path=/;Max-Age=604800;secure;HttpOnly;SameSite=Lax"}, {Http::Headers::get().Location.get(), ""}, }; @@ -3309,11 +3425,13 @@ TEST_F(OAuth2Test, MixedCookieSameSiteWithDisabled) { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly;SameSite=None"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=None"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=some-refresh-token;path=/;Max-Age=604800;secure;HttpOnly;SameSite=Strict"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + + ";path=/;Max-Age=604800;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().Location.get(), ""}, }; @@ -3350,11 +3468,14 @@ TEST_F(OAuth2Test, MixedCookieSameSiteWithoutDisabled) { {Http::Headers::get().SetCookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly;SameSite=None"}, {Http::Headers::get().SetCookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().SetCookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().SetCookie.get(), - "RefreshToken=some-refresh-token;path=/;Max-Age=604800;secure;HttpOnly;SameSite=Lax"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + + ";path=/;Max-Age=604800;secure;HttpOnly;SameSite=Lax"}, {Http::Headers::get().Location.get(), ""}, }; @@ -3432,11 +3553,14 @@ TEST_F(OAuth2Test, CookiesDeletedWhenTokensCleared) { {Http::Headers::get().Cookie.get(), "OauthExpires=1600;path=/;Max-Age=600;secure;HttpOnly;SameSite=None"}, {Http::Headers::get().Cookie.get(), - "BearerToken=access_code;path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, + "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN + + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().Cookie.get(), - "IdToken=some-id-token;path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, + "IdToken=" + TEST_ENCRYPTED_ID_TOKEN + + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, {Http::Headers::get().Cookie.get(), - "RefreshToken=some-refresh-token;path=/;Max-Age=604800;secure;HttpOnly;SameSite=Lax"}, + "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + + ";path=/;Max-Age=604800;secure;HttpOnly;SameSite=Lax"}, {Http::Headers::get().Cookie.get(), "OauthNonce=" + TEST_CSRF_TOKEN + ";path=/;Max-Age=600;secure;HttpOnly;SameSite=Strict"}, }; @@ -3467,6 +3591,83 @@ TEST_F(OAuth2Test, CookiesDeletedWhenTokensCleared) { filter_->onGetAccessTokenSuccess("", "", "", expiredTime); } +// Ensure that the token cookies are decrypted before forwarding the request +TEST_F(OAuth2Test, CookiesDecryptedBeforeForwarding) { + // Initialize with use_refresh_token set to false + init(getConfig(true /* forward_bearer_token */)); + + // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. + test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); + + Http::TestRequestHeaderMapImpl request_headers{ + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Path.get(), "/original_path?var1=1&var2=2"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Cookie.get(), "OauthHMAC=4TKyxPV/F7yyvr0XgJ2bkWFOc8t4IOFen1k29b84MAQ="}, + {Http::Headers::get().Cookie.get(), "OauthExpires=1600"}, + {Http::Headers::get().Cookie.get(), "BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, + {Http::Headers::get().Cookie.get(), "IdToken=" + TEST_ENCRYPTED_ID_TOKEN}, + {Http::Headers::get().Cookie.get(), "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN}, + {Http::Headers::get().Cookie.get(), "OauthNonce=" + TEST_CSRF_TOKEN}, + }; + + // cookie-validation mocking + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); + + // return reference mocking + std::string access_token{"access_code"}; + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(access_token)); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + + // Expect the request headers to be updated with the decrypted tokens + auto cookies = Http::Utility::parseCookies(request_headers); + EXPECT_EQ(cookies.at("BearerToken"), "access_code"); + EXPECT_EQ(cookies.at("IdToken"), "some-id-token"); + EXPECT_EQ(cookies.at("RefreshToken"), "some-refresh-token"); +} + +// Ensure that the token cookies are decrypted before forwarding the request +TEST_F(OAuth2Test, CookiesDecryptedBeforeForwardingWithEncryptionDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.oauth2_encrypt_tokens", "false"}}); + + // Initialize with use_refresh_token set to false + init(getConfig(true /* forward_bearer_token */)); + + // Set SystemTime to a fixed point so we get consistent HMAC encodings between test runs. + test_time_.setSystemTime(SystemTime(std::chrono::seconds(0))); + + Http::TestRequestHeaderMapImpl request_headers{ + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Path.get(), "/original_path?var1=1&var2=2"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Cookie.get(), "OauthHMAC=4TKyxPV/F7yyvr0XgJ2bkWFOc8t4IOFen1k29b84MAQ="}, + {Http::Headers::get().Cookie.get(), "OauthExpires=1600"}, + {Http::Headers::get().Cookie.get(), "BearerToken=access_code"}, + {Http::Headers::get().Cookie.get(), "IdToken=some-id-token"}, + {Http::Headers::get().Cookie.get(), "RefreshToken=some-refresh-token"}, + {Http::Headers::get().Cookie.get(), "OauthNonce=" + TEST_CSRF_TOKEN}, + }; + + // cookie-validation mocking + EXPECT_CALL(*validator_, setParams(_, _)); + EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); + + // return reference mocking + std::string access_token{"access_code"}; + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(access_token)); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + + // Expect the request headers to be updated with the decrypted tokens + auto cookies = Http::Utility::parseCookies(request_headers); + EXPECT_EQ(cookies.at("BearerToken"), "access_code"); + EXPECT_EQ(cookies.at("IdToken"), "some-id-token"); + EXPECT_EQ(cookies.at("RefreshToken"), "some-refresh-token"); +} + } // namespace Oauth2 } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/oauth2/oauth_integration_test.cc b/test/extensions/filters/http/oauth2/oauth_integration_test.cc index a95107ce415d2..5b8dd1005ba71 100644 --- a/test/extensions/filters/http/oauth2/oauth_integration_test.cc +++ b/test/extensions/filters/http/oauth2/oauth_integration_test.cc @@ -15,6 +15,7 @@ namespace Extensions { namespace HttpFilters { namespace Oauth2 { namespace { + static const std::string TEST_STATE_CSRF_TOKEN = "8c18b8fcf575b593.qE67JkhE3H/0rpNYWCkQXX65Yzk5gEe7uETE3m8tylY="; // {"url":"http://traffic.example.com/not/_oauth","csrf_token":"${extracted}"} @@ -31,6 +32,69 @@ static const std::string TEST_ENCRYPTED_CODE_VERIFIER = "Fc1bBwAAAAAVzVsHAAAAACcWO_WnprqLTdaCdFE7rj83_Jej1OihEIfOcQJFRCQZirutZ-XL7LK2G2KgRnVCCA"; static const std::string TEST_ENCRYPTED_CODE_VERIFIER_1 = "Fc1bBwAAAAAVzVsHAAAAANRgXgBre6UErcWdPGZOl-o0px-SribGBqMNhaB6Smp-pjDSB20RXanapU6gVN4E1A"; +static const std::string TEST_ENCRYPTED_ACCESS_TOKEN = + "Fc1bBwAAAAAVzVsHAAAAALw-JhWF2XQOvdUKxWoMN1w"; // "bar" +static const std::string TEST_ENCRYPTED_REFRESH_TOKEN = + "Fc1bBwAAAAAVzVsHAAAAAM9NnfacsjScJzcyWlSKX6E"; // "foo" + +/** + * Decrypt an AES-256-CBC encrypted string. + */ +std::string decrypt(absl::string_view encrypted, absl::string_view secret) { + // Decode the Base64Url-encoded input + std::string decoded = Base64Url::decode(encrypted); + std::vector combined(decoded.begin(), decoded.end()); + + if (combined.size() <= 16) { + return ""; + } + + // Extract the IV (first 16 bytes) + std::vector iv(combined.begin(), combined.begin() + 16); + + // Extract the ciphertext (remaining bytes) + std::vector ciphertext(combined.begin() + 16, combined.end()); + + // Generate the key from the secret using SHA-256 + std::vector key(SHA256_DIGEST_LENGTH); + SHA256(reinterpret_cast((std::string(secret)).c_str()), secret.size(), + key.data()); + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + RELEASE_ASSERT(ctx, "Failed to create context"); + + std::vector plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH); + int len = 0, plaintext_len = 0; + + // Initialize decryption operation + if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) { + EVP_CIPHER_CTX_free(ctx); + return ""; + } + + // Decrypt the ciphertext + if (EVP_DecryptUpdate(ctx, plaintext.data(), &len, ciphertext.data(), ciphertext.size()) != 1) { + EVP_CIPHER_CTX_free(ctx); + return ""; + } + plaintext_len += len; + + // Finalize decryption + if (EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len) != 1) { + EVP_CIPHER_CTX_free(ctx); + return ""; + } + + plaintext_len += len; + + EVP_CIPHER_CTX_free(ctx); + + // Resize to actual plaintext length + plaintext.resize(plaintext_len); + + return std::string(plaintext.begin(), plaintext.end()); +} + class OauthIntegrationTest : public HttpIntegrationTest, public Grpc::GrpcClientIntegrationParamTest { public: @@ -39,6 +103,7 @@ class OauthIntegrationTest : public HttpIntegrationTest, skip_tag_extraction_rule_check_ = true; enableHalfClose(true); } + envoy::service::discovery::v3::DiscoveryResponse genericSecretResponse(absl::string_view name, absl::string_view value) { envoy::extensions::transport_sockets::tls::v3::Secret secret; @@ -278,12 +343,13 @@ name: oauth validate_headers.addReferenceKey( Http::Headers::get().Cookie, absl::StrCat(default_cookie_names_.oauth_expires_, "=", expires)); - validate_headers.addReferenceKey(Http::Headers::get().Cookie, - absl::StrCat(default_cookie_names_.bearer_token_, "=", token)); - validate_headers.addReferenceKey( Http::Headers::get().Cookie, - absl::StrCat(default_cookie_names_.refresh_token_, "=", refreshToken)); + absl::StrCat(default_cookie_names_.bearer_token_, "=", decrypt(token, hmac_secret))); + + validate_headers.addReferenceKey(Http::Headers::get().Cookie, + absl::StrCat(default_cookie_names_.refresh_token_, "=", + decrypt(refreshToken, hmac_secret))); OAuth2CookieValidator validator{api_->timeSource(), default_cookie_names_, ""}; validator.setParams(validate_headers, std::string(hmac_secret)); @@ -407,10 +473,15 @@ name: oauth codec_client_ = makeHttpConnection(lookupPort("http")); Http::TestRequestHeaderMapImpl headers{ - {":method", "GET"}, {":path", "/request1"}, - {":scheme", "http"}, {"x-forwarded-proto", "http"}, - {":authority", "authority"}, {"Cookie", "RefreshToken=efddf321;BearerToken=ff1234fc"}, - {":authority", "authority"}, {"authority", "Bearer token"}}; + {":method", "GET"}, + {":path", "/request1"}, + {":scheme", "http"}, + {"x-forwarded-proto", "http"}, + {":authority", "authority"}, + {"Cookie", "RefreshToken=" + TEST_ENCRYPTED_REFRESH_TOKEN + + ";BearerToken=" + TEST_ENCRYPTED_ACCESS_TOKEN}, + {":authority", "authority"}, + {"authority", "Bearer token"}}; auto encoder_decoder = codec_client_->startRequest(headers); request_encoder_ = &encoder_decoder.first; diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 05bce0201599e..4d9fa4cf18fc6 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -91,6 +91,7 @@ ETB ETX FS FIXME +decrypted gperf HEXDIG HEXDIGIT