5#include <nlohmann/json.hpp>
19namespace mcp::auth::openssl {
22 std::string protected_header;
24 std::string signature;
26 std::string signing_input()
const {
return protected_header +
"." + payload; }
30 std::string algorithm;
31 std::optional<std::string> key_id;
32 std::optional<std::string> type;
33 std::optional<std::string> content_type;
41 std::vector<unsigned char> payload;
42 std::vector<unsigned char> signature;
47inline std::optional<std::string> jose_optional_string(
48 const nlohmann::json&
object,
const char* key) {
49 const auto iter =
object.find(key);
50 if (iter ==
object.end() || iter->is_null()) {
53 if (!iter->is_string()) {
56 return iter->get<std::string>();
59inline MetadataMap jose_extension_metadata(
const nlohmann::json&
object) {
61 for (
auto iter =
object.begin(); iter !=
object.end(); ++iter) {
62 const auto key = iter.key();
63 if (key ==
"alg" || key ==
"kid" || key ==
"typ" || key ==
"cty" ||
64 key ==
"jwk" || key ==
"x5c" || key ==
"x5t" || key ==
"x5t#S256" ||
68 if (iter->is_string()) {
69 result.emplace(key, iter->get<std::string>());
70 }
else if (iter->is_boolean()) {
71 result.emplace(key, iter->get<
bool>() ?
"true" :
"false");
72 }
else if (iter->is_number() || iter->is_object() || iter->is_array()) {
73 result.emplace(key, iter->dump());
79inline core::Result<nlohmann::json> parse_json_object(std::string_view data,
80 std::string message) {
83 value = nlohmann::json::parse(data.begin(), data.end());
84 }
catch (
const nlohmann::json::parse_error& error) {
85 return core::unexpected(make_jose_error(JoseErrorCode::kInvalidJoseHeader,
86 std::move(message), error.what()));
88 if (!value.is_object()) {
89 return core::unexpected(
90 make_jose_error(JoseErrorCode::kInvalidJoseHeader, std::move(message)));
97inline core::Result<CompactJwsParts> parse_compact_jws_parts(
98 std::string_view compact_jws) {
99 const auto first_dot = compact_jws.find(
'.');
100 if (first_dot == std::string_view::npos) {
101 return core::unexpected(make_jose_error(
102 JoseErrorCode::kInvalidCompactJws,
103 "compact JWS must contain three dot-separated segments"));
105 const auto second_dot = compact_jws.find(
'.', first_dot + 1);
106 if (second_dot == std::string_view::npos ||
107 compact_jws.find(
'.', second_dot + 1) != std::string_view::npos) {
108 return core::unexpected(
109 make_jose_error(JoseErrorCode::kInvalidCompactJws,
110 "compact JWS must contain exactly three segments"));
113 CompactJwsParts parts;
114 parts.protected_header = std::string(compact_jws.substr(0, first_dot));
115 parts.payload = std::string(
116 compact_jws.substr(first_dot + 1, second_dot - first_dot - 1));
117 parts.signature = std::string(compact_jws.substr(second_dot + 1));
119 if (parts.protected_header.empty()) {
120 return core::unexpected(
121 make_jose_error(JoseErrorCode::kInvalidCompactJws,
122 "compact JWS protected header segment is required"));
124 if (parts.payload.empty()) {
125 return core::unexpected(
126 make_jose_error(JoseErrorCode::kInvalidCompactJws,
127 "compact JWS payload segment is required"));
129 if (parts.signature.empty()) {
130 return core::unexpected(
131 make_jose_error(JoseErrorCode::kInvalidCompactJws,
132 "compact JWS signature segment is required"));
138inline core::Result<JoseProtectedHeader> parse_jose_protected_header(
139 std::string_view protected_header_segment) {
140 auto decoded = base64url_decode_to_string(protected_header_segment);
141 if (!decoded.has_value()) {
142 return core::unexpected(decoded.error());
145 auto header_json = detail::parse_json_object(
146 *decoded,
"JWS protected header must be a JSON object");
147 if (!header_json.has_value()) {
148 return core::unexpected(header_json.error());
151 JoseProtectedHeader header;
152 header.raw = std::move(*header_json);
153 const auto algorithm = detail::jose_optional_string(header.raw,
"alg");
154 if (!algorithm.has_value() || algorithm->empty()) {
155 return core::unexpected(
156 make_jose_error(JoseErrorCode::kInvalidJoseHeader,
157 "JWS protected header alg is required"));
159 header.algorithm = *algorithm;
160 header.key_id = detail::jose_optional_string(header.raw,
"kid");
161 header.type = detail::jose_optional_string(header.raw,
"typ");
162 header.content_type = detail::jose_optional_string(header.raw,
"cty");
163 header.metadata = detail::jose_extension_metadata(header.raw);
167inline core::Result<DecodedCompactJws> decode_compact_jws(
168 std::string_view compact_jws) {
169 auto parts = parse_compact_jws_parts(compact_jws);
170 if (!parts.has_value()) {
171 return core::unexpected(parts.error());
174 auto header = parse_jose_protected_header(parts->protected_header);
175 if (!header.has_value()) {
176 return core::unexpected(header.error());
179 auto payload = base64url_decode(parts->payload);
180 if (!payload.has_value()) {
181 return core::unexpected(payload.error());
183 auto signature = base64url_decode(parts->signature);
184 if (!signature.has_value()) {
185 return core::unexpected(signature.error());
188 DecodedCompactJws decoded;
189 decoded.parts = std::move(*parts);
190 decoded.protected_header = std::move(*header);
191 decoded.payload = std::move(*payload);
192 decoded.signature = std::move(*signature);
Shared lightweight value types for cxxmcp auth contracts.
JOSE base64url helpers shared by optional OpenSSL auth code.
Shared result and error primitives used by the public cxxmcp SDK.