cxxmcp 1.1.6
C++ MCP SDK
Loading...
Searching...
No Matches
jws_verify.hpp
Go to the documentation of this file.
1// Copyright (c) 2025 [caomengxuan666]
2
3#pragma once
4
5#include <openssl/ec.h>
6#include <openssl/ecdsa.h>
7#include <openssl/evp.h>
8
9#include <memory>
10#include <optional>
11#include <string>
12#include <string_view>
13#include <vector>
14
18
21
22namespace mcp::auth::openssl {
23
24namespace detail {
25
27 void operator()(ECDSA_SIG* value) const noexcept { ECDSA_SIG_free(value); }
28};
29
31 void operator()(EVP_MD_CTX* value) const noexcept { EVP_MD_CTX_free(value); }
32};
33
34using EcdsaSigPtr = std::unique_ptr<ECDSA_SIG, EcdsaSigDeleter>;
35using EvpMdCtxPtr = std::unique_ptr<EVP_MD_CTX, EvpMdCtxDeleter>;
36
37inline const EVP_MD* digest_for_jose_algorithm(std::string_view algorithm) {
38 if (algorithm == "RS256" || algorithm == "ES256") {
39 return EVP_sha256();
40 }
41 if (algorithm == "ES384") {
42 return EVP_sha384();
43 }
44 if (algorithm == "ES512") {
45 return EVP_sha512();
46 }
47 return nullptr;
48}
49
50inline core::Result<std::vector<unsigned char>> ecdsa_raw_signature_to_der(
51 const std::vector<unsigned char>& raw_signature,
52 std::string_view algorithm) {
53 const std::size_t coordinate_size =
54 ec_coordinate_size_for_jose_algorithm(algorithm);
55 if (coordinate_size == 0 || raw_signature.size() != coordinate_size * 2) {
56 return core::unexpected(
57 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
58 "JWS ECDSA signature has an invalid size"));
59 }
60
61 BignumPtr r(BN_bin2bn(raw_signature.data(), static_cast<int>(coordinate_size),
62 nullptr));
63 BignumPtr s(BN_bin2bn(raw_signature.data() + coordinate_size,
64 static_cast<int>(coordinate_size), nullptr));
65 if (!r || !s) {
66 return core::unexpected(
67 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
68 "failed to decode JWS ECDSA signature"));
69 }
70
71 EcdsaSigPtr signature(ECDSA_SIG_new());
72 if (!signature ||
73 ECDSA_SIG_set0(signature.get(), r.release(), s.release()) != 1) {
74 return core::unexpected(
75 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
76 "failed to build JWS ECDSA signature"));
77 }
78
79 const int der_size = i2d_ECDSA_SIG(signature.get(), nullptr);
80 if (der_size <= 0) {
81 return core::unexpected(
82 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
83 "failed to size JWS ECDSA signature"));
84 }
85
86 std::vector<unsigned char> der(static_cast<std::size_t>(der_size));
87 unsigned char* cursor = der.data();
88 if (i2d_ECDSA_SIG(signature.get(), &cursor) != der_size) {
89 return core::unexpected(
90 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
91 "failed to encode JWS ECDSA signature"));
92 }
93 return der;
94}
95
96inline core::Result<std::vector<unsigned char>> ecdsa_der_signature_to_raw(
97 const std::vector<unsigned char>& der_signature,
98 std::string_view algorithm) {
99 const std::size_t coordinate_size =
100 ec_coordinate_size_for_jose_algorithm(algorithm);
101 if (coordinate_size == 0) {
102 return core::unexpected(
103 make_jose_error(JoseErrorCode::kUnsupportedJoseAlgorithm,
104 "unsupported JWS ECDSA signature algorithm"));
105 }
106
107 const unsigned char* cursor = der_signature.data();
108 EcdsaSigPtr signature(d2i_ECDSA_SIG(
109 nullptr, &cursor,
110 static_cast<long>(der_signature.size()))); // NOLINT(runtime/int)
111 if (!signature) {
112 return core::unexpected(
113 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
114 "failed to decode JWS ECDSA DER signature"));
115 }
116
117 const BIGNUM* r = nullptr;
118 const BIGNUM* s = nullptr;
119 ECDSA_SIG_get0(signature.get(), &r, &s);
120 if (r == nullptr || s == nullptr) {
121 return core::unexpected(
122 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
123 "JWS ECDSA DER signature is missing r/s"));
124 }
125
126 std::vector<unsigned char> raw;
127 auto r_bytes = bignum_to_fixed_bytes(r, coordinate_size);
128 if (!r_bytes.has_value()) {
129 return core::unexpected(r_bytes.error());
130 }
131 auto s_bytes = bignum_to_fixed_bytes(s, coordinate_size);
132 if (!s_bytes.has_value()) {
133 return core::unexpected(s_bytes.error());
134 }
135 raw.reserve(r_bytes->size() + s_bytes->size());
136 raw.insert(raw.end(), r_bytes->begin(), r_bytes->end());
137 raw.insert(raw.end(), s_bytes->begin(), s_bytes->end());
138 return raw;
139}
140
141inline bool is_ecdsa_jose_algorithm(std::string_view algorithm) {
142 return algorithm == "ES256" || algorithm == "ES384" || algorithm == "ES512";
143}
144
145} // namespace detail
146
148 std::optional<std::string> required_algorithm;
149 std::optional<std::string> required_key_id;
150};
151
152inline core::Result<core::Unit> verify_compact_jws_signature(
153 std::string_view compact_jws, const JsonWebKey& jwk,
154 const JwsVerificationOptions& options = {}) {
155 auto decoded = decode_compact_jws(compact_jws);
156 if (!decoded.has_value()) {
157 return core::unexpected(decoded.error());
158 }
159
160 const auto& header = decoded->protected_header;
161 if (options.required_algorithm.has_value() &&
162 header.algorithm != *options.required_algorithm) {
163 return core::unexpected(
164 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
165 "JWS alg does not match required algorithm"));
166 }
167 if (jwk.algorithm.has_value() && header.algorithm != *jwk.algorithm) {
168 return core::unexpected(
169 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
170 "JWS alg does not match JWK alg"));
171 }
172 if (options.required_key_id.has_value() &&
173 (!header.key_id.has_value() ||
174 *header.key_id != *options.required_key_id)) {
175 return core::unexpected(
176 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
177 "JWS kid does not match required key id"));
178 }
179 if (jwk.key_id.has_value() &&
180 (!header.key_id.has_value() || *header.key_id != *jwk.key_id)) {
181 return core::unexpected(
182 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
183 "JWS kid does not match JWK kid"));
184 }
185
186 auto public_key = public_key_from_jwk(jwk, header.algorithm);
187 if (!public_key.has_value()) {
188 return core::unexpected(public_key.error());
189 }
190
191 const EVP_MD* digest = detail::digest_for_jose_algorithm(header.algorithm);
192 if (digest == nullptr) {
193 return core::unexpected(
194 make_jose_error(JoseErrorCode::kUnsupportedJoseAlgorithm,
195 "unsupported JWS signature algorithm"));
196 }
197
198 std::vector<unsigned char> signature = decoded->signature;
199 if (detail::is_ecdsa_jose_algorithm(header.algorithm)) {
200 auto der = detail::ecdsa_raw_signature_to_der(signature, header.algorithm);
201 if (!der.has_value()) {
202 return core::unexpected(der.error());
203 }
204 signature = std::move(*der);
205 }
206
207 detail::EvpMdCtxPtr context(EVP_MD_CTX_new());
208 const std::string signing_input = decoded->parts.signing_input();
209 if (!context ||
210 EVP_DigestVerifyInit(context.get(), nullptr, digest, nullptr,
211 public_key->key.get()) <= 0 ||
212 EVP_DigestVerifyUpdate(context.get(), signing_input.data(),
213 signing_input.size()) <= 0) {
214 return core::unexpected(
215 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
216 "failed to initialize JWS signature verification"));
217 }
218
219 const int verified =
220 EVP_DigestVerifyFinal(context.get(), signature.data(), signature.size());
221 if (verified == 1) {
222 return core::Unit{};
223 }
224 if (verified == 0) {
225 return core::unexpected(
226 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
227 "JWS signature verification failed"));
228 }
229 return core::unexpected(
230 make_jose_error(JoseErrorCode::kSignatureVerificationFailed,
231 "OpenSSL failed while verifying JWS signature"));
232}
233
234} // namespace mcp::auth::openssl
OpenSSL conversion helpers for public JSON Web Keys.
Safe parsing boundaries for JOSE compact JWS values.
Shared result and error primitives used by the public cxxmcp SDK.
tl::expected< T, Error > Result
Alias for the SDK result type.
Definition result.hpp:64
Public JSON Web Key value model.
Definition jwks.hpp:24
Definition jws_verify.hpp:147