18namespace mcp::auth::openssl {
20enum class JoseErrorCode {
21 kInvalidBase64Url = 2001,
22 kInvalidCompactJws = 2002,
23 kInvalidJoseHeader = 2003,
25 kUnsupportedJoseAlgorithm = 2005,
26 kSignatureVerificationFailed = 2006,
27 kJwtClaimValidationFailed = 2007,
30inline core::Error make_jose_error(JoseErrorCode code, std::string message,
31 std::string detail = {}) {
32 return core::Error{
static_cast<int>(code), std::move(message),
33 std::move(detail), std::string(AuthErrorCategory)};
36inline std::string base64url_encode_bytes(
const unsigned char* data,
38 static constexpr char kAlphabet[] =
39 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
41 output.reserve(((size + 2) / 3) * 4);
43 for (std::size_t index = 0; index < size; index += 3) {
44 const auto remaining = size - index;
45 const auto b0 = data[index];
46 const auto b1 = remaining > 1 ? data[index + 1] : 0;
47 const auto b2 = remaining > 2 ? data[index + 2] : 0;
49 output.push_back(kAlphabet[(b0 >> 2) & 0x3F]);
50 output.push_back(kAlphabet[((b0 & 0x03) << 4) | ((b1 >> 4) & 0x0F)]);
52 output.push_back(kAlphabet[((b1 & 0x0F) << 2) | ((b2 >> 6) & 0x03)]);
55 output.push_back(kAlphabet[b2 & 0x3F]);
61inline std::string base64url_encode(std::string_view data) {
62 return base64url_encode_bytes(
63 reinterpret_cast<const unsigned char*
>(data.data()), data.size());
66inline std::string base64url_encode(
const std::vector<unsigned char>& data) {
67 return base64url_encode_bytes(data.data(), data.size());
72inline int base64url_decode_value(
char ch) {
73 if (ch >=
'A' && ch <=
'Z') {
76 if (ch >=
'a' && ch <=
'z') {
79 if (ch >=
'0' && ch <=
'9') {
93inline core::Result<std::vector<unsigned char>> base64url_decode(
94 std::string_view input) {
95 if (input.find(
'=') != std::string_view::npos) {
96 return core::unexpected(
97 make_jose_error(JoseErrorCode::kInvalidBase64Url,
98 "base64url input must not contain padding"));
100 if (input.size() % 4 == 1) {
101 return core::unexpected(
102 make_jose_error(JoseErrorCode::kInvalidBase64Url,
103 "base64url input has invalid length"));
106 std::vector<unsigned char> output;
107 output.reserve((input.size() * 3) / 4);
109 std::uint32_t accumulator = 0;
111 for (
const char ch : input) {
112 const int value = detail::base64url_decode_value(ch);
114 return core::unexpected(make_jose_error(
115 JoseErrorCode::kInvalidBase64Url,
116 "base64url input contains an invalid character", std::string(1, ch)));
119 accumulator = (accumulator << 6) | static_cast<std::uint32_t>(value);
124 static_cast<unsigned char>((accumulator >> bits) & 0xFF));
128 const std::uint32_t unused_mask =
129 bits == 0 ? 0U : ((1U <<
static_cast<unsigned int>(bits)) - 1U);
130 if ((accumulator & unused_mask) != 0U) {
131 return core::unexpected(
132 make_jose_error(JoseErrorCode::kInvalidBase64Url,
133 "base64url input contains non-zero trailing bits"));
139inline core::Result<std::string> base64url_decode_to_string(
140 std::string_view input) {
141 auto decoded = base64url_decode(input);
142 if (!decoded.has_value()) {
143 return core::unexpected(decoded.error());
145 return std::string(decoded->begin(), decoded->end());
Shared lightweight value types for cxxmcp auth contracts.
Shared result and error primitives used by the public cxxmcp SDK.