cxxmcp 1.1.6
C++ MCP SDK
Loading...
Searching...
No Matches
jws.hpp
Go to the documentation of this file.
1// Copyright (c) 2025 [caomengxuan666]
2
3#pragma once
4
5#include <nlohmann/json.hpp>
6#include <optional>
7#include <string>
8#include <string_view>
9#include <utility>
10#include <vector>
11
13#include "cxxmcp/auth/types.hpp"
15
18
19namespace mcp::auth::openssl {
20
22 std::string protected_header;
23 std::string payload;
24 std::string signature;
25
26 std::string signing_input() const { return protected_header + "." + payload; }
27};
28
30 std::string algorithm;
31 std::optional<std::string> key_id;
32 std::optional<std::string> type;
33 std::optional<std::string> content_type;
34 nlohmann::json raw;
35 MetadataMap metadata;
36};
37
39 CompactJwsParts parts;
40 JoseProtectedHeader protected_header;
41 std::vector<unsigned char> payload;
42 std::vector<unsigned char> signature;
43};
44
45namespace detail {
46
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()) {
51 return std::nullopt;
52 }
53 if (!iter->is_string()) {
54 return std::nullopt;
55 }
56 return iter->get<std::string>();
57}
58
59inline MetadataMap jose_extension_metadata(const nlohmann::json& object) {
60 MetadataMap result;
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" ||
65 key == "crit") {
66 continue;
67 }
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());
74 }
75 }
76 return result;
77}
78
79inline core::Result<nlohmann::json> parse_json_object(std::string_view data,
80 std::string message) {
81 nlohmann::json value;
82 try {
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()));
87 }
88 if (!value.is_object()) {
89 return core::unexpected(
90 make_jose_error(JoseErrorCode::kInvalidJoseHeader, std::move(message)));
91 }
92 return value;
93}
94
95} // namespace detail
96
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"));
104 }
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"));
111 }
112
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));
118
119 if (parts.protected_header.empty()) {
120 return core::unexpected(
121 make_jose_error(JoseErrorCode::kInvalidCompactJws,
122 "compact JWS protected header segment is required"));
123 }
124 if (parts.payload.empty()) {
125 return core::unexpected(
126 make_jose_error(JoseErrorCode::kInvalidCompactJws,
127 "compact JWS payload segment is required"));
128 }
129 if (parts.signature.empty()) {
130 return core::unexpected(
131 make_jose_error(JoseErrorCode::kInvalidCompactJws,
132 "compact JWS signature segment is required"));
133 }
134
135 return parts;
136}
137
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());
143 }
144
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());
149 }
150
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"));
158 }
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);
164 return header;
165}
166
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());
172 }
173
174 auto header = parse_jose_protected_header(parts->protected_header);
175 if (!header.has_value()) {
176 return core::unexpected(header.error());
177 }
178
179 auto payload = base64url_decode(parts->payload);
180 if (!payload.has_value()) {
181 return core::unexpected(payload.error());
182 }
183 auto signature = base64url_decode(parts->signature);
184 if (!signature.has_value()) {
185 return core::unexpected(signature.error());
186 }
187
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);
193 return decoded;
194}
195
196} // namespace mcp::auth::openssl
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.