Skip to content

Commit

Permalink
net: add modp_b64
Browse files Browse the repository at this point in the history
Fix a bug with server side's authorization in https auth.

see https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT
  • Loading branch information
Chilledheart committed May 26, 2024
1 parent 804b197 commit f252196
Show file tree
Hide file tree
Showing 12 changed files with 976 additions and 42 deletions.
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2711,6 +2711,21 @@ if (BORINGSSL_BUILD_TESTS)
USES_TERMINAL)
endif()

# *****************************************************************************************
# modp_b64 Library
# *****************************************************************************************
add_library(modp_b64 STATIC
third_party/modp_b64/modp_b64.h
third_party/modp_b64/modp_b64_data.h
third_party/modp_b64/modp_b64.cc
)
target_include_directories(modp_b64 PUBLIC
third_party/modp_b64)

target_compile_options(modp_b64 PRIVATE "-Wno-error")

set(SUPPORT_LIBS modp_b64 ${SUPPORT_LIBS})

# *****************************************************************************************
# mbedssl Library
# *****************************************************************************************
Expand Down Expand Up @@ -4647,6 +4662,7 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
file(READ third_party/libunwind/trunk/LICENSE.TXT _LIBUNWIND_LICENSE)
file(READ third_party/lss/LICENSE _LSS_LICENSE)
file(READ third_party/mbedtls/LICENSE _MBEDTLS_LICENSE)
file(READ third_party/modp_b64/LICENSE _MODP_B64_LICENSE)
file(READ third_party/protobuf/LICENSE _PROTOBUF_LICENSE)
file(READ third_party/quiche/src/LICENSE _QUICHE_LICENSE)
file(READ third_party/re2/LICENSE _RE2_LICENSE)
Expand Down
4 changes: 4 additions & 0 deletions LICENSE.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@

@_MBEDTLS_LICENSE@

=== modp_b64 ==

@_MODP_B64_LICENSE@

=== protobuf ==

@_PROTOBUF_LICENSE@
Expand Down
8 changes: 4 additions & 4 deletions src/cli/cli_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ static std::vector<http2::adapter::Header> GenerateHeaders(std::vector<std::pair
}

static std::string GetProxyAuthorizationIdentity() {
std::string result;
auto user_pass = absl::StrCat(absl::GetFlag(FLAGS_username), ":", absl::GetFlag(FLAGS_password));
Base64Encode(user_pass, &result);
return result;
return Base64Encode(
absl::Span<uint8_t>(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(user_pass.c_str())), user_pass.size()));
}

static bool g_nonindex_codes_initialized;
Expand Down Expand Up @@ -2166,9 +2165,10 @@ void CliConnection::connected() {
std::string hdr = absl::StrFormat(
"CONNECT %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Proxy-Authorization: %s\r\n"
"Proxy-Connection: Close\r\n"
"\r\n",
hostname_and_port.c_str(), hostname_and_port.c_str());
hostname_and_port.c_str(), hostname_and_port.c_str(), absl::StrCat("basic ", GetProxyAuthorizationIdentity()));
// write variable address directly as https header
upstream_.push_back(hdr.data(), hdr.size());
} else {
Expand Down
85 changes: 58 additions & 27 deletions src/net/base64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,89 @@

#include "net/base64.hpp"

#include <base/strings/string_util.h>
#include <stddef.h>
#include "core/logging.hpp"

#include "third_party/boringssl/src/include/openssl/base64.h"
#include "third_party/modp_b64/modp_b64.h"

using namespace gurl_base;

namespace net {

std::string Base64Encode(const uint8_t* data, size_t length) {
std::string output;
size_t out_len;
EVP_EncodedLength(&out_len, length); // makes room for null byte
output.resize(out_len);
namespace {

// modp_b64_encode_len() returns at least 1, so output[0] is safe to use.
const size_t output_size = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(&(output[0])), data, length);
ModpDecodePolicy GetModpPolicy(Base64DecodePolicy policy) {
switch (policy) {
case Base64DecodePolicy::kStrict:
return ModpDecodePolicy::kStrict;
case Base64DecodePolicy::kForgiving:
return ModpDecodePolicy::kForgiving;
}
DCHECK(false) << "Unexpect Base64 Decode Policy: " << (int)policy;
return ModpDecodePolicy::kStrict;
}

output.resize(output_size);
} // namespace

std::string Base64Encode(absl::Span<uint8_t> input) {
std::string output;
Base64EncodeAppend(input, &output);
return output;
}

void Base64Encode(std::string_view input, std::string* output) {
*output = Base64Encode(reinterpret_cast<const uint8_t*>(&*input.begin()), input.size());
void Base64EncodeAppend(absl::Span<uint8_t> input, std::string* output) {
// Ensure `modp_b64_encode_data_len` will not overflow.
CHECK_LE(input.size(), MODP_B64_MAX_INPUT_LEN);
size_t encode_data_len = modp_b64_encode_data_len(input.size());

size_t prefix_len = output->size();
output->resize(encode_data_len + prefix_len);

const size_t output_size =
modp_b64_encode_data(output->data() + prefix_len, reinterpret_cast<const char*>(input.data()), input.size());
CHECK_EQ(output->size(), prefix_len + output_size);
}

bool Base64Decode(std::string_view input, std::string* output) {
bool Base64Decode(std::string_view input, std::string* output, Base64DecodePolicy policy) {
std::string temp;
size_t out_len;
EVP_DecodedLength(&out_len, input.size());
temp.resize(out_len);
temp.resize(modp_b64_decode_len(input.size()));

// does not null terminate result since result is binary data!
size_t input_size = input.size();
int output_size = EVP_DecodeBase64(reinterpret_cast<uint8_t*>(&(temp[0])), &out_len, out_len,
reinterpret_cast<const uint8_t*>(input.data()), input_size);
if (output_size <= 0)
size_t output_size = modp_b64_decode(&(temp[0]), input.data(), input_size, GetModpPolicy(policy));

// Forgiving mode requires whitespace to be stripped prior to decoding.
// We don't do that in the above code to ensure that the "happy path" of
// input without whitespace is as fast as possible. Since whitespace in input
// will always cause `modp_b64_decode` to fail, just handle whitespace
// stripping on failure. This is not much slower than just scanning for
// whitespace first, even for input with whitespace.
if (output_size == MODP_B64_ERROR && policy == Base64DecodePolicy::kForgiving) {
// We could use `output` here to avoid an allocation when decoding is done
// in-place, but it violates the API contract that `output` is only modified
// on success.
std::string input_without_whitespace;
RemoveChars(input, kInfraAsciiWhitespace, &input_without_whitespace);
output_size = modp_b64_decode(&(temp[0]), input_without_whitespace.data(), input_without_whitespace.size(),
GetModpPolicy(policy));
}

if (output_size == MODP_B64_ERROR)
return false;

temp.resize(output_size);
output->swap(temp);
return true;
}

absl::optional<std::vector<uint8_t>> Base64Decode(std::string_view input) {
std::vector<uint8_t> ret;
size_t out_len;
EVP_DecodedLength(&out_len, input.size());
ret.resize(out_len);
std::optional<std::vector<uint8_t>> Base64Decode(std::string_view input) {
std::vector<uint8_t> ret(modp_b64_decode_len(input.size()));

size_t input_size = input.size();
int output_size =
EVP_DecodeBase64(ret.data(), &out_len, out_len, reinterpret_cast<const uint8_t*>(input.data()), input_size);
if (output_size <= 0)
return absl::nullopt;
size_t output_size = modp_b64_decode(reinterpret_cast<char*>(ret.data()), input.data(), input_size);
if (output_size == MODP_B64_ERROR)
return std::nullopt;

ret.resize(output_size);
return ret;
Expand Down
22 changes: 18 additions & 4 deletions src/net/base64.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,33 @@
#include <vector>

#include <absl/types/optional.h>
#include <absl/types/span.h>

namespace net {

// Encodes the input binary data in base64.
std::string Base64Encode(const uint8_t* data, size_t length);
std::string Base64Encode(absl::Span<uint8_t> input);

// Encodes the input string in base64.
void Base64Encode(std::string_view input, std::string* output);
// Encodes the input binary data in base64 and appends it to the output.
void Base64EncodeAppend(absl::Span<uint8_t> input, std::string* output);

// Decodes the base64 input string. Returns true if successful and false
// otherwise. The output string is only modified if successful. The decoding can
// be done in-place.
bool Base64Decode(std::string_view input, std::string* output);
enum class Base64DecodePolicy {
// Input should match the output format of Base64Encode. i.e.
// - Input length should be divisible by 4
// - Maximum of 2 padding characters
// - No non-base64 characters.
kStrict,

// Matches https://infra.spec.whatwg.org/#forgiving-base64-decode.
// - Removes all ascii whitespace
// - Maximum of 2 padding characters
// - Allows input length not divisible by 4 if no padding chars are added.
kForgiving,
};
bool Base64Decode(std::string_view input, std::string* output, Base64DecodePolicy policy = Base64DecodePolicy::kStrict);

// Decodes the base64 input string. Returns `std::nullopt` if unsuccessful.
std::optional<std::vector<uint8_t>> Base64Decode(std::string_view input);
Expand Down
6 changes: 6 additions & 0 deletions src/net/http_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ void HttpRequestParser::ProcessHeaders(const quiche::BalsaHeaders& headers) {
if (key == "Proxy-Connection") {
connection_ = std::string(value);
}
if (key == "Proxy-Authorization") {
proxy_authorization_ = std::string(value);
}
}
}

Expand Down Expand Up @@ -540,6 +543,9 @@ int HttpRequestParser::OnReadHttpRequestHeaderValue(http_parser* parser, const c
if (self->http_field_ == "Content-Type") {
self->content_type_ = std::string(buf, len);
}
if (self->http_field_ == "Proxy-Authorization") {
self->proxy_authorization_ = std::string(buf, len);
}
return 0;
}

Expand Down
6 changes: 6 additions & 0 deletions src/net/http_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class HttpRequestParser : public quiche::BalsaVisitorInterface {
uint64_t content_length() const { return headers_.content_length(); }
const std::string& content_type() const { return content_type_; }
const std::string& connection() const { return connection_; }
const std::string& proxy_authorization() const { return proxy_authorization_; }
bool transfer_encoding_is_chunked() const { return headers_.transfer_encoding_is_chunked(); }

void ReforgeHttpRequest(std::string* header,
Expand Down Expand Up @@ -106,6 +107,8 @@ class HttpRequestParser : public quiche::BalsaVisitorInterface {
std::string content_type_;
/// copy of connection
std::string connection_;
/// copy of proxy_authorization
std::string proxy_authorization_;

bool first_byte_processed_ = false;
bool headers_done_ = false;
Expand Down Expand Up @@ -138,6 +141,7 @@ class HttpRequestParser {
uint64_t content_length() const;
const std::string& content_type() const { return content_type_; }
std::string_view connection() const;
const std::string& proxy_authorization() const { return proxy_authorization_; }
bool transfer_encoding_is_chunked() const;

int status_code() const;
Expand Down Expand Up @@ -170,6 +174,8 @@ class HttpRequestParser {
bool http_is_connect_ = false;
/// copy of content type
std::string content_type_;
/// copy of proxy_authorization
std::string proxy_authorization_;
};

class HttpResponseParser : public HttpRequestParser {
Expand Down
30 changes: 23 additions & 7 deletions src/server/server_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,20 @@ static std::vector<http2::adapter::Header> GenerateHeaders(std::vector<std::pair
return response_vector;
}

static std::string GetProxyAuthorizationIdentity() {
std::string result;
auto user_pass = absl::StrCat(absl::GetFlag(FLAGS_username), ":", absl::GetFlag(FLAGS_password));
Base64Encode(user_pass, &result);
return result;
static constexpr std::string_view kBasicAuthPrefix = "basic ";
static bool VerifyProxyAuthorizationIdentity(std::string_view auth) {
if (auth.size() <= kBasicAuthPrefix.size()) {
return false;
}
if (ToLowerASCII(auth.substr(0, kBasicAuthPrefix.size())) != kBasicAuthPrefix) {
return false;
}
auth.remove_prefix(kBasicAuthPrefix.size());
std::string pass;
if (!Base64Decode(auth, &pass)) {
return false;
}
return pass == absl::StrCat(absl::GetFlag(FLAGS_username), ":", absl::GetFlag(FLAGS_password));
}

#endif
Expand Down Expand Up @@ -285,7 +294,7 @@ bool ServerConnection::OnEndHeadersForStream(http2::adapter::Http2StreamId strea
return false;
}
auto auth = request_map_["proxy-authorization"s];
if (auth != absl::StrCat("basic ", GetProxyAuthorizationIdentity())) {
if (!VerifyProxyAuthorizationIdentity(auth)) {
LOG(INFO) << "Connection (server) " << connection_id() << " from: " << peer_endpoint << " Unexpected auth token.";
return false;
}
Expand Down Expand Up @@ -587,7 +596,14 @@ void ServerConnection::OnReadHandshakeViaHttps() {
http_is_connect_ = parser.is_connect();

if (http_host_.size() > TLSEXT_MAXLEN_host_name) {
LOG(WARNING) << "Connection (server) " << connection_id() << " too long domain name: " << http_host_;
LOG(INFO) << "Connection (server) " << connection_id() << " too long domain name: " << http_host_;
ec = asio::error::invalid_argument;
OnDisconnect(ec);
return;
}

if (!VerifyProxyAuthorizationIdentity(parser.proxy_authorization())) {
LOG(INFO) << "Connection (server) " << connection_id() << " Unexpected auth token.";
ec = asio::error::invalid_argument;
OnDisconnect(ec);
return;
Expand Down
33 changes: 33 additions & 0 deletions third_party/modp_b64/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
* MODP_B64 - High performance base64 encoder/decoder
* Version 1.3 -- 17-Mar-2006
* http://modp.com/release/base64
*
* Copyright (c) 2005, 2006 Nick Galbreath -- nickg [at] modp [dot] com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of the modp.com nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Loading

0 comments on commit f252196

Please sign in to comment.