epee: fail digest auth cleanly when MD5 is unavailable
What changed, and why it matters
This change makes Monero's HTTP digest authentication code handle failures in the MD5 hashing functions more gracefully. Previously, if OpenSSL's MD5 operations failed (for example, because MD5 was disabled in the cryptographic library), the code would silently continue with invalid or empty hash values, potentially allowing authentication to proceed incorrectly. Now the code checks each hashing step and aborts the authentication attempt cleanly if MD5 cannot be computed.
Treat as a defensive hardening fix with possible security implications. Review whether prior behavior could allow authentication bypass when MD5 is unavailable, and consider whether a CVE is warranted if an exploitable bypass existed. Users should upgrade to include this fix, especially those building against OpenSSL configurations where MD5 may be disabled or fail.
Security signals we found
OpenSSL EVP return values now checked and propagated
Digest computation changed from raw return to boost::optional to signal failure
Client and server auth paths abort on digest failure
Prevents silent continuation with potentially invalid MD5 hashes
No explicit CVE or security advisory referenced in commit
Evidence from the diff
The patch modifies contrib/epee/src/http_auth.cpp to propagate errors from OpenSSL EVP_DigestInit_ex, EVP_DigestUpdate, and EVP_DigestFinal_ex calls. The digest functor now returns boost::optional
Changed components
contrib/epee/src/http_auth.cppHTTP digest authentication client pathHTTP digest authentication server verification pathOpenSSL EVP_MD_CTX MD5 operationsInspect captured patch +71 / −25
diff --git a/contrib/epee/src/http_auth.cpp b/contrib/epee/src/http_auth.cpp
index 5e21d5d..d9c392f 100644
--- a/contrib/epee/src/http_auth.cpp
+++ b/contrib/epee/src/http_auth.cpp
@@ -42,6 +42,7 @@
#include <boost/range/algorithm/find_if.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <boost/range/join.hpp>
+#include <boost/optional/optional.hpp>
#include <boost/spirit/include/karma_generate.hpp>
#include <boost/spirit/include/karma_uint.hpp>
#include <boost/spirit/include/qi_alternative.hpp>
@@ -112,40 +113,52 @@ namespace
struct update
{
template<typename T>
- void operator()(const T& arg) const
+ bool operator()(const T& arg) const
{
const boost::iterator_range<const char*> data(boost::as_literal(arg));
- EVP_DigestUpdate(
+ return EVP_DigestUpdate(
ctx,
reinterpret_cast<const std::uint8_t*>(data.begin()),
data.size()
- );
+ ) == 1;
}
- void operator()(const std::string& arg) const
+ bool operator()(const std::string& arg) const
{
- (*this)(boost::string_ref(arg));
+ return (*this)(boost::string_ref(arg));
}
- void operator()(const epee::wipeable_string& arg) const
+ bool operator()(const epee::wipeable_string& arg) const
{
- EVP_DigestUpdate(
+ return EVP_DigestUpdate(
ctx,
reinterpret_cast<const std::uint8_t*>(arg.data()),
arg.size()
- );
+ ) == 1;
}
EVP_MD_CTX *ctx;
};
template<typename... T>
- std::array<char, 32> operator()(const T&... args) const
- {
+ boost::optional<std::array<char, 32>> operator()(const T&... args) const
+ {
std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> ctx(EVP_MD_CTX_new(), &EVP_MD_CTX_free);
- EVP_DigestInit(ctx.get(), EVP_md5());
- boost::fusion::for_each(std::tie(args...), update{ctx.get()});
+ if (!ctx)
+ return boost::none;
+
+ if (EVP_DigestInit_ex(ctx.get(), EVP_md5(), nullptr) != 1)
+ return boost::none;
+
+ bool ok = true;
+ boost::fusion::for_each(std::tie(args...), [&] (const auto& arg) {
+ ok = ok && update{ctx.get()}(arg);
+ });
+
+ if (!ok)
+ return boost::none;
std::array<std::uint8_t, 16> digest{{}};
- EVP_DigestFinal(ctx.get(), digest.data(), NULL);
+ if (EVP_DigestFinal_ex(ctx.get(), digest.data(), nullptr) != 1)
+ return boost::none;
return epee::to_hex::array(digest);
}
};
@@ -254,12 +267,21 @@ namespace
std::string operator()(const http::http_client_auth::session& user,
const boost::string_ref method, const boost::string_ref uri) const
{
- const auto response = digest(
- generate_a1(digest, user), u8":", user.server.nonce, u8":", digest(method, u8":", uri)
- );
+ const auto a1 = generate_a1(digest, user);
+ if (!a1)
+ return {};
+
+ const auto a2 = digest(method, u8":", uri);
+ if (!a2)
+ return {};
+
+ const auto response = digest(*a1, u8":", user.server.nonce, u8":", *a2);
+ if (!response)
+ return {};
+
std::string out{};
out.reserve(client_reserve_size);
- init_client_value(out, Digest::name, user, uri, response);
+ init_client_value(out, Digest::name, user, uri, *response);
return out;
}
private:
@@ -301,12 +323,24 @@ namespace
return {};
const std::string cnonce = epee::string_encoding::base64_encode(rbuf.data(), rbuf.size());
+
+ const auto a1 = generate_a1(digest, user);
+ if (!a1)
+ return {};
+
+ const auto a2 = digest(method, u8":", uri);
+ if (!a2)
+ return {};
+
const auto response = digest(
- generate_a1(digest, user), u8":", user.server.nonce, u8":", nc, u8":", cnonce, u8":auth:", digest(method, u8":", uri)
+ *a1, u8":", user.server.nonce, u8":", nc, u8":", cnonce, u8":auth:", *a2
);
+ if (!response)
+ return {};
+
out.clear();
- init_client_value(out, Digest::name, user, uri, response);
+ init_client_value(out, Digest::name, user, uri, *response);
add_field(out, u8"qop", ceref(u8"auth"));
add_field(out, u8"nc", nc);
add_field(out, u8"cnonce", quoted_(cnonce));
@@ -517,13 +551,13 @@ namespace
struct has_valid_response
{
template<typename Digest, typename Result>
- Result generate_old_response(Digest digest, const Result& key, const Result& auth) const
+ boost::optional<Result> generate_old_response(Digest digest, const Result& key, const Result& auth) const
{
return digest(key, u8":", request.nonce, u8":", auth);
}
template<typename Digest, typename Result>
- Result generate_new_response(Digest digest, const Result& key, const Result& auth) const
+ boost::optional<Result> generate_new_response(Digest digest, const Result& key, const Result& auth) const
{
return digest(
key, u8":", request.nonce, u8":", request.nc, u8":", request.cnonce, u8":", request.qop, u8":", auth
@@ -543,19 +577,27 @@ namespace
(request.algorithm.empty() && std::is_same<md5_, Digest>::value))
{
auto key = generate_a1(digest, user.credentials, auth_realm);
+ if (!key)
+ return false;
if (boost::ends_with(request.algorithm, sess_algo, ascii_iequal))
{
- key = digest(key, u8":", request.nonce, u8":", request.cnonce);
+ key = digest(*key, u8":", request.nonce, u8":", request.cnonce);
+ if (!key)
+ return false;
}
auto auth = digest(method, u8":", request.uri);
+ if (!auth)
+ return false;
if (request.qop.empty())
{
- return check(generate_old_response(std::move(digest), std::move(key), std::move(auth)));
+ const auto response = generate_old_response(std::move(digest), std::move(*key), std::move(*auth));
+ return response && check(*response);
}
else if (boost::equals(ceref(u8"auth"), request.qop, ascii_iequal))
{
- return check(generate_new_response(std::move(digest), std::move(key), std::move(auth)));
+ const auto response = generate_new_response(std::move(digest), std::move(*key), std::move(*auth));
+ return response && check(*response);
}
}
return false;
@@ -777,7 +819,11 @@ namespace epee
if (user->server.generator)
{
++(user->counter);
- return std::make_pair(std::string(client_auth_field), user->server.generator(*user, method, uri));
+ std::string auth = user->server.generator(*user, method, uri);
+ if (auth.empty())
+ return boost::none;
+
+ return std::make_pair(std::string(client_auth_field), std::move(auth));
}
return boost::none;
}
Why this scored 33/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.