Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions include/base64.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,6 @@ base64_encode_init(struct base64_encode_ctx *ctx);
void
base64url_encode_init(struct base64_encode_ctx *ctx);

/* Encodes a single byte. Returns amount of output (always 1 or 2). */
size_t
base64_encode_single(struct base64_encode_ctx *ctx,
char *dst,
uint8_t src);

/* Returns the number of output characters. DST should point to an

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Given how many cases were missed by recent commit 74b1276 and followup commit 19a590c, I think this PR should rename base64_encode_update() declared below to base64_encode_updateXXX(). We clearly cannot rely on PR authors finding remaining relevant cases; we need the compiler to flag them for us. Hopefully, after this PR, there will not be many such cases left.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this PR should rename base64_encode_update() declared below to base64_encode_updateXXX()

No. base64_encode_update() is part of the libnettle API, of which this file is an extract for systems that do not carry it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No. base64_encode_update() is part of the libnettle API, of which this file is an extract for systems that do not carry it.

Given how many cases were missed by recent commit 74b1276 and followup commit 19a590c, I think a better reaction would look more like this:

Sounds good, and I will also add a trivial wrapper with the same base64_encode_updateXXX() name to keep libnettle-based builds happy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sounds good, but I do not plan to add a trivial wrapper.
This PR is about implementing a new API, and doing MVP clients to ensure it works as intended, as per common practice. Once this effort completes, there should be very few mentions of base64[^ ]* (case-sensitive) in the codebase.

Main exceptions will be:

  • lib/sspi/sspwin32.cc: it's third-party library
  • src/auth/negotiate/kerberos/negotiate_kerberos_auth.cc: binary not using SBuf (for now)
  • src/auth/negotiate/kerberos/negotiate_kerberos_pac.cc: binary not using SBuf (for now)
  • src/auth/ntlm/fake/ntlm_fake_auth.cc: binary not using SBuf (for now)

git grep base64 will be good enough to identify all trouble spots; there is no added value from the proposed out-of-scope change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

git grep base64 will be good enough to identify all trouble spots

That logic has failed us twice already. We could get lucky the third time, of course, but I recommend avoiding that risk instead.

there is no added value from the proposed out-of-scope change

IMO, there clearly is value in explicitly marking problematic APIs as such. And the proposed change can easily be viewed as in-scope -- it self-documents which API must be used in new code whenever feasible (to avoid the XXX). If this PR claims that hiding the problematic API is out of scope, then please make sure this PR explicitly documents which of the two available APIs the folks should use whenever feasible.

* area of size at least BASE64_ENCODE_LENGTH(length). */
size_t
Expand Down
2 changes: 1 addition & 1 deletion lib/base64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ base64_encode_init(struct base64_encode_ctx *ctx)
}

/* Encodes a single byte. */
size_t
static size_t
base64_encode_single(struct base64_encode_ctx *ctx,
char *dst,
uint8_t src)
Expand Down
15 changes: 4 additions & 11 deletions src/HttpHeader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
/* DEBUG: section 55 HTTP Header */

#include "squid.h"
#include "anyp/Base64.h"
#include "base/Assure.h"
#include "base/CharacterSet.h"
#include "base/EnumIterator.h"
#include "base/Raw.h"
#include "base64.h"
#include "globals.h"
#include "http/ContentLengthInterpreter.h"
#include "HttpHdrCc.h"
Expand Down Expand Up @@ -1430,18 +1430,11 @@ HttpHeader::getAuthToken(Http::HdrType id, const char *auth_scheme) const
if (!*field) /* no authorization cookie */
return nil;

const auto fieldLen = strlen(field);
SBuf result;
char *decodedAuthToken = result.rawAppendStart(BASE64_DECODE_LENGTH(fieldLen));
struct base64_decode_ctx ctx;
base64_decode_init(&ctx);
size_t decodedLen = 0;
if (!base64_decode_update(&ctx, &decodedLen, reinterpret_cast<uint8_t*>(decodedAuthToken), fieldLen, field) ||
!base64_decode_final(&ctx)) {
try {
return Base64Decode(field, strlen(field));
} catch (const DecodeException &) {
return nil;
}
result.rawAppendFinish(decodedAuthToken, decodedLen);
return result;
}

ETag
Expand Down
17 changes: 17 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,23 @@ tests_testSBufList_LDADD = \
$(XTRA_LIBS)
tests_testSBufList_LDFLAGS = $(LIBADD_DL)

check_PROGRAMS += tests/testBase64
tests_testBase64_SOURCES = \
tests/testBase64.cc
nodist_tests_testBase64_SOURCES = \
tests/stub_debug.cc \
tests/stub_libmem.cc
tests_testBase64_LDADD = \
sbuf/libsbuf.la \
base/libbase.la \
anyp/libanyp.la \
$(top_builddir)/lib/libmiscencoding.la \
$(LIBCPPUNIT_LIBS) \
$(COMPAT_LIB) \
$(XTRA_LIBS) \
$(LIBNETTLE_LIBS)
tests_testBase64_LDFLAGS = $(LIBADD_DL)

check_PROGRAMS += tests/testString
tests_testString_SOURCES = \
tests/testString.cc
Expand Down
29 changes: 8 additions & 21 deletions src/adaptation/icap/ModXact.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "adaptation/icap/ModXact.h"
#include "adaptation/icap/ServiceRep.h"
#include "adaptation/Initiator.h"
#include "anyp/Base64.h"
#include "auth/UserRequest.h"
#include "base/TextException.h"
#include "base64.h"
Expand All @@ -32,6 +33,7 @@
#include "MasterXaction.h"
#include "parser/Tokenizer.h"
#include "sbuf/Stream.h"
#include "sbuf/StringConvert.h"

// flow and terminology:
// HTTP| --> receive --> encode --> write --> |network
Expand Down Expand Up @@ -1399,20 +1401,10 @@ void Adaptation::Icap::ModXact::makeRequestHeaders(MemBuf &buf)
String vh=virgin.header->header.getById(Http::HdrType::PROXY_AUTHORIZATION);
buf.appendf("Proxy-Authorization: " SQUIDSTRINGPH "\r\n", SQUIDSTRINGPRINT(vh));
} else if (request->extacl_user.size() > 0 && request->extacl_passwd.size() > 0) {
const auto userLen = request->extacl_user.size();
const auto passwdLen = request->extacl_passwd.size();
// +1 for the ':' separator between user and passwd
const auto plainLen = userLen + 1 + passwdLen;
if (plainLen > MAX_LOGIN_SZ)
throw TextException("extacl credentials too long for Proxy-Authorization", Here());
char base64buf[base64_encode_len(MAX_LOGIN_SZ)];
struct base64_encode_ctx ctx;
base64_encode_init(&ctx);
auto resultLen = base64_encode_update(&ctx, base64buf, userLen, reinterpret_cast<const uint8_t*>(request->extacl_user.rawBuf()));
resultLen += base64_encode_update(&ctx, base64buf+resultLen, 1, reinterpret_cast<const uint8_t*>(":"));
resultLen += base64_encode_update(&ctx, base64buf+resultLen, passwdLen, reinterpret_cast<const uint8_t*>(request->extacl_passwd.rawBuf()));
resultLen += base64_encode_final(&ctx, base64buf+resultLen);
buf.appendf("Proxy-Authorization: Basic %.*s\r\n", (int)resultLen, base64buf);
auto credentials=StringToSBuf(request->extacl_user);
credentials.append(':').append(request->extacl_passwd.rawBuf(), request->extacl_passwd.size());
const auto encoded = Base64Encode(credentials);
buf.appendf("Proxy-Authorization: Basic " SQUIDSBUFPH "\r\n", SQUIDSBUFPRINT(encoded));
}

// share the cross-transactional database records if needed
Expand Down Expand Up @@ -1554,9 +1546,6 @@ void Adaptation::Icap::ModXact::makeAllowHeader(MemBuf &buf)
void Adaptation::Icap::ModXact::makeUsernameHeader(const HttpRequest *request, MemBuf &buf)
{
#if USE_AUTH
struct base64_encode_ctx ctx;
base64_encode_init(&ctx);

const char *value = nullptr;
if (request->auth_user_request != nullptr) {
value = request->auth_user_request->username();
Expand All @@ -1566,10 +1555,8 @@ void Adaptation::Icap::ModXact::makeUsernameHeader(const HttpRequest *request, M

if (value) {
if (TheConfig.client_username_encode) {
char base64buf[base64_encode_len(MAX_LOGIN_SZ)];
size_t resultLen = base64_encode_update(&ctx, base64buf, strlen(value), reinterpret_cast<const uint8_t*>(value));
resultLen += base64_encode_final(&ctx, base64buf+resultLen);
buf.appendf("%s: %.*s\r\n", TheConfig.client_username_header, (int)resultLen, base64buf);
const auto base64buf=Base64Encode(value,strlen(value));
buf.appendf("%s: " SQUIDSBUFPH "\r\n", TheConfig.client_username_header, SQUIDSBUFPRINT(base64buf));
} else
buf.appendf("%s: %s\r\n", TheConfig.client_username_header, value);
}
Expand Down
51 changes: 51 additions & 0 deletions src/anyp/Base64.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (C) 1996-2026 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/

#include "squid.h"
#include "anyp/Base64.h"
#include "base64.h"
#include "sbuf/SBuf.h"

SBuf Base64Encode(const char *input, size_t length)
{
SBuf result;
const auto encodedLength = BASE64_ENCODE_RAW_LENGTH(length);
char *buf = result.rawAppendStart(encodedLength);
base64_encode_raw(buf, length, reinterpret_cast<const uint8_t *>(input));
result.rawAppendFinish(buf, encodedLength);
return result;
}

SBuf Base64Encode(const SBuf &input)
{
return Base64Encode(input.rawContent(), input.length());
}

SBuf Base64Decode(const char *input, size_t length)
{
struct base64_decode_ctx ctx;
base64_decode_init(&ctx);

SBuf result;
const auto maxDecodedLength = BASE64_DECODE_LENGTH(length);
uint8_t *buf = reinterpret_cast<uint8_t *>(result.rawAppendStart(maxDecodedLength));

size_t decodedLength = 0;
if (!base64_decode_update(&ctx, &decodedLength, buf, length, input))
throw DecodeException("base64 decode error: invalid input", Here());
if (!base64_decode_final(&ctx))
throw DecodeException("base64 decode error: incomplete input", Here());

result.rawAppendFinish(reinterpret_cast<char *>(buf), decodedLength);
return result;
}

SBuf Base64Decode(const SBuf &input)
{
return Base64Decode(input.rawContent(), input.length());
}
29 changes: 29 additions & 0 deletions src/anyp/Base64.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 1996-2026 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/

#ifndef SQUID_SRC_ANYP_BASE64_H
#define SQUID_SRC_ANYP_BASE64_H

#include "base/TextException.h"
#include "sbuf/forward.h"

/// Thrown by Base64Decode() when the input is not valid base64.
class DecodeException : public TextException

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please do not introduce a custom exception type to report decoding errors, especially when that type is effectively unused outside of unit tests. Instead, return std::optional<SBuf>, especially since the only non-test caller immediately handles decoding errors (i.e. without propagating them a lot further up the stack to some distant high-level code).

This is the second time we are discussing a recent attempt to throw from a decoding function. The first one resulted in a AnyP::Uri::Decode()-related bug fixed in commit ad365ed. I am not claiming there is a stable pattern here, but we should be mindful of that first failure. Let's drop exceptions from this code, at least as a starting point.

{
public:
using TextException::TextException;
};

SBuf Base64Encode(const char *input, size_t length);
SBuf Base64Encode(const SBuf &input);

/// Decodes a base64-encoded string. Throws DecodeException on invalid input.
SBuf Base64Decode(const char *input, size_t length);
SBuf Base64Decode(const SBuf &input);

#endif /* SQUID_SRC_ANYP_BASE64_H */
2 changes: 2 additions & 0 deletions src/anyp/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ include $(top_srcdir)/src/Common.am
noinst_LTLIBRARIES = libanyp.la

libanyp_la_SOURCES = \
Base64.cc \
Base64.h \
Host.cc \
Host.h \
PortCfg.cc \
Expand Down
8 changes: 8 additions & 0 deletions src/auth/ntlm/fake/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ libexec_PROGRAMS= ntlm_fake_auth

ntlm_fake_auth_SOURCES = ntlm_fake_auth.cc
ntlm_fake_auth_LDADD= \
$(top_builddir)/src/anyp/libanyp.la \
$(top_builddir)/src/sbuf/libsbuf.la \
$(top_builddir)/src/debug/libdebug.la \
$(top_builddir)/src/error/liberror.la \
$(top_builddir)/src/comm/libminimal.la \
$(top_builddir)/src/mem/libminimal.la \
$(top_builddir)/src/base/libbase.la \
$(top_builddir)/src/time/libtime.la \
$(top_builddir)/lib/ntlmauth/libntlmauth.la \
$(top_builddir)/lib/libmiscencoding.la \
$(COMPAT_LIB) \
Expand Down
14 changes: 6 additions & 8 deletions src/auth/ntlm/fake/ntlm_fake_auth.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
#define IGNORANCE_IS_BLISS

#include "squid.h"
#include "anyp/Base64.h"
#include "base64.h"
#include "helper/protocol_defines.h"
#include "ntlmauth/ntlmauth.h"
#include "ntlmauth/support_bits.cci"
#include "sbuf/SBuf.h"

#include <cctype>
#include <chrono>
Expand Down Expand Up @@ -201,18 +203,14 @@ main(int argc, char *argv[])

len = sizeof(chal) - sizeof(chal.payload) + le16toh(chal.target.maxlen);

struct base64_encode_ctx eCtx;
base64_encode_init(&eCtx);
char *data = static_cast<char *>(xcalloc(base64_encode_len(len), 1));
size_t blen = base64_encode_update(&eCtx, data, len, reinterpret_cast<const uint8_t *>(&chal));
blen += base64_encode_final(&eCtx, data+blen);
const auto base64EncodedChallenge=Base64Encode(reinterpret_cast<const char *>(&chal), len);

if (NTLM_packet_debug_enabled) {
printf("TT %.*s\n", (int)blen, data);
printf("TT " SQUIDSBUFPH "\n", SQUIDSBUFPRINT(base64EncodedChallenge));
debug("sending 'TT' to squid with data:\n");
hex_dump((unsigned char *)&chal, len);
} else
SEND3("TT %.*s", (int)blen, data);
safe_free(data);
SEND3("TT " SQUIDSBUFPH, SQUIDSBUFPRINT(base64EncodedChallenge));

} else if (strncmp(buf, "KK ", 3) == 0) {
if (!packet) {
Expand Down
14 changes: 3 additions & 11 deletions src/format/Format.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

#include "squid.h"
#include "AccessLogEntry.h"
#include "base64.h"
#include "anyp/Base64.h"
#include "client_side.h"
#include "comm/Connection.h"
#include "error/Detail.h"
Expand Down Expand Up @@ -556,16 +556,8 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS
case LFT_CLIENT_HANDSHAKE:
if (al->request && al->request->clientConnectionManager.valid()) {
const auto &handshake = al->request->clientConnectionManager->preservedClientData;
if (const auto rawLength = handshake.length()) {
// add 1 byte to optimize the c_str() conversion below
char *buf = sb.rawAppendStart(base64_encode_len(rawLength) + 1);

struct base64_encode_ctx ctx;
base64_encode_init(&ctx);
auto encLength = base64_encode_update(&ctx, buf, rawLength, reinterpret_cast<const uint8_t*>(handshake.rawContent()));
encLength += base64_encode_final(&ctx, buf + encLength);

sb.rawAppendFinish(buf, encLength);
if (!handshake.isEmpty()) {
sb = Base64Encode(handshake);
out = sb.c_str();
}
}
Expand Down
Loading
Loading