CCF
Loading...
Searching...
No Matches
http_jwt.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
5#include "ccf/crypto/base64.h"
7#include "ccf/http_consts.h"
8#include "http_parser.h"
9
10#define FMT_HEADER_ONLY
11#include <fmt/format.h>
12#include <optional>
13#include <string>
14
15namespace http
16{
17 enum class JwtCryptoAlgorithm : uint8_t
18 {
19 RS256,
20 ES256,
21 };
24 {{JwtCryptoAlgorithm::RS256, "RS256"},
25 {JwtCryptoAlgorithm::ES256, "ES256"}});
26
27 struct JwtHeader
28 {
30 std::string kid;
31 };
34
36 {
37 size_t exp{};
38 std::string iss;
39 std::optional<size_t> nbf;
40 std::optional<std::string> tid;
41 };
45
47 {
48 public:
49 struct Token
50 {
51 nlohmann::json header;
53 nlohmann::json payload;
55 std::vector<uint8_t> signature;
56 std::string_view signed_content;
57 };
58
59 static bool parse_auth_scheme(
60 std::string_view& auth_header_value, std::string& error_reason)
61 {
62 auto next_space = auth_header_value.find(' ');
63 if (next_space == std::string::npos)
64 {
65 error_reason = "Authorization header only contains one field";
66 return false;
67 }
68 auto auth_scheme = auth_header_value.substr(0, next_space);
69 if (auth_scheme != ccf::http::auth::BEARER_AUTH_SCHEME)
70 {
71 error_reason = fmt::format(
72 "Authorization header does not have {} scheme",
73 ccf::http::auth::BEARER_AUTH_SCHEME);
74 return false;
75 }
76 auth_header_value = auth_header_value.substr(next_space + 1);
77 return true;
78 }
79
80 static std::vector<uint8_t> parse_jwt_b64url(
81 std::string_view b64url, std::string_view part, std::string& error_reason)
82 {
83 try
84 {
85 auto raw = ccf::crypto::raw_from_b64url(b64url);
86 if (raw.empty())
87 {
88 error_reason = fmt::format("JWT part is empty ({})", part);
89 return {};
90 }
91 return raw;
92 }
93 catch (const std::exception& e)
94 {
95 error_reason =
96 fmt::format("Failed to parse base64url in JWT ({})", part);
97 return {};
98 }
99 }
100
101 static std::optional<Token> parse_token(
102 std::string_view& token, std::string& error_reason)
103 {
104 constexpr char separator = '.';
105 size_t first_dot = token.find(separator);
106 size_t second_dot = std::string::npos;
107 if (first_dot != std::string::npos)
108 {
109 second_dot = token.find(separator, first_dot + 1);
110 }
111 size_t extra_dot = std::string::npos;
112 if (second_dot != std::string::npos)
113 {
114 extra_dot = token.find(separator, second_dot + 1);
115 }
116 if (
117 first_dot == std::string::npos || second_dot == std::string::npos ||
118 extra_dot != std::string::npos)
119 {
120 error_reason = "Malformed JWT: must contain exactly 3 parts";
121 return std::nullopt;
122 }
123 size_t header_size = first_dot;
124 size_t payload_size = second_dot - first_dot - 1;
125 std::string_view header_b64url = token.substr(0, header_size);
126 std::string_view payload_b64url =
127 token.substr(first_dot + 1, payload_size);
128 std::string_view signature_b64url = token.substr(second_dot + 1);
129
130 auto header_raw = parse_jwt_b64url(header_b64url, "header", error_reason);
131 if (header_raw.empty())
132 {
133 return std::nullopt;
134 }
135
136 auto payload_raw =
137 parse_jwt_b64url(payload_b64url, "payload", error_reason);
138 if (payload_raw.empty())
139 {
140 return std::nullopt;
141 }
142
143 auto signature_raw =
144 parse_jwt_b64url(signature_b64url, "signature", error_reason);
145 if (signature_raw.empty())
146 {
147 return std::nullopt;
148 }
149
150 auto signed_content = token.substr(0, second_dot);
151 nlohmann::json header;
152 nlohmann::json payload;
153 try
154 {
155 header = nlohmann::json::parse(header_raw);
156 payload = nlohmann::json::parse(payload_raw);
157 }
158 catch (const nlohmann::json::parse_error& e)
159 {
160 error_reason =
161 fmt::format("JWT header or payload is not valid JSON: {}", e.what());
162 return std::nullopt;
163 }
164 if (!header.is_object() || !payload.is_object())
165 {
166 error_reason = "JWT header or payload is not an object";
167 return std::nullopt;
168 }
169 JwtHeader header_typed;
170 try
171 {
172 header_typed = header.get<JwtHeader>();
173 }
174 catch (const ccf::JsonParseError& e)
175 {
176 error_reason =
177 fmt::format("JWT header does not follow schema: {}", e.describe());
178 return std::nullopt;
179 }
180 JwtPayload payload_typed;
181 try
182 {
183 payload_typed = payload.get<JwtPayload>();
184 }
185 catch (const ccf::JsonParseError& e)
186 {
187 error_reason = fmt::format(
188 "JWT payload is missing required field: {}", e.describe());
189 return std::nullopt;
190 }
191 Token parsed = {
192 header,
193 header_typed,
194 payload,
195 payload_typed,
196 signature_raw,
197 signed_content};
198 return parsed;
199 }
200
201 static std::optional<Token> extract_token(
202 const ccf::http::HeaderMap& headers, std::string& error_reason)
203 {
204 const auto auth_it = headers.find(ccf::http::headers::AUTHORIZATION);
205 if (auth_it == headers.end())
206 {
207 error_reason =
208 fmt::format("Missing {} header", ccf::http::headers::AUTHORIZATION);
209 return std::nullopt;
210 }
211 std::string_view token = auth_it->second;
212 if (!parse_auth_scheme(token, error_reason))
213 {
214 return std::nullopt;
215 }
216 auto parsed = parse_token(token, error_reason);
217 return parsed;
218 }
219 };
220}
Definition json.h:26
Definition http_jwt.h:47
static std::optional< Token > parse_token(std::string_view &token, std::string &error_reason)
Definition http_jwt.h:101
static std::optional< Token > extract_token(const ccf::http::HeaderMap &headers, std::string &error_reason)
Definition http_jwt.h:201
static bool parse_auth_scheme(std::string_view &auth_header_value, std::string &error_reason)
Definition http_jwt.h:59
static std::vector< uint8_t > parse_jwt_b64url(std::string_view b64url, std::string_view part, std::string &error_reason)
Definition http_jwt.h:80
#define DECLARE_JSON_REQUIRED_FIELDS(TYPE,...)
Definition json.h:736
#define DECLARE_JSON_TYPE(TYPE)
Definition json.h:685
#define DECLARE_JSON_TYPE_WITH_OPTIONAL_FIELDS(TYPE)
Definition json.h:712
#define DECLARE_JSON_OPTIONAL_FIELDS(TYPE,...)
Definition json.h:811
#define DECLARE_JSON_ENUM(TYPE,...)
Definition json.h:864
std::vector< uint8_t > raw_from_b64url(const std::string_view &b64url_string)
Definition base64.cpp:17
std::map< std::string, std::string, std::less<> > HeaderMap
Definition http_header_map.h:10
Definition error_reporter.h:8
JwtCryptoAlgorithm
Definition http_jwt.h:18
Definition http_jwt.h:28
JwtCryptoAlgorithm alg
Definition http_jwt.h:29
std::string kid
Definition http_jwt.h:30
Definition http_jwt.h:36
std::optional< std::string > tid
Definition http_jwt.h:40
size_t exp
Definition http_jwt.h:37
std::string iss
Definition http_jwt.h:38
std::optional< size_t > nbf
Definition http_jwt.h:39
Definition http_jwt.h:50
JwtPayload payload_typed
Definition http_jwt.h:54
nlohmann::json header
Definition http_jwt.h:51
std::vector< uint8_t > signature
Definition http_jwt.h:55
JwtHeader header_typed
Definition http_jwt.h:52
nlohmann::json payload
Definition http_jwt.h:53
std::string_view signed_content
Definition http_jwt.h:56