1
1
#ifndef JWT_CPP_BASE_H
2
2
#define JWT_CPP_BASE_H
3
3
4
+ #include < algorithm>
4
5
#include < array>
5
6
#include < stdexcept>
6
7
#include < string>
8
+ #include < vector>
7
9
8
10
#ifdef __has_cpp_attribute
9
11
#if __has_cpp_attribute(fallthrough)
@@ -21,7 +23,10 @@ namespace jwt {
21
23
*/
22
24
namespace alphabet {
23
25
/* *
24
- * \brief valid list of characted when working with [Base64](https://tools.ietf.org/html/rfc3548)
26
+ * \brief valid list of character when working with [Base64](https://datatracker.ietf.org/doc/html/rfc4648#section-4)
27
+ *
28
+ * As directed in [X.509 Parameter](https://datatracker.ietf.org/doc/html/rfc7517#section-4.7) certificate chains are
29
+ * base64-encoded as per [Section 4 of RFC4648](https://datatracker.ietf.org/doc/html/rfc4648#section-4)
25
30
*/
26
31
struct base64 {
27
32
static const std::array<char , 64 >& data () {
@@ -38,7 +43,13 @@ namespace jwt {
38
43
}
39
44
};
40
45
/* *
41
- * \brief valid list of characted when working with [Base64URL](https://tools.ietf.org/html/rfc4648)
46
+ * \brief valid list of character when working with [Base64URL](https://tools.ietf.org/html/rfc4648#section-5)
47
+ *
48
+ * As directed by [RFC 7519 Terminology](https://datatracker.ietf.org/doc/html/rfc7519#section-2) set the definition of Base64URL
49
+ * encoding as that in [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515#section-2) that states:
50
+ *
51
+ * > Base64 encoding using the URL- and filename-safe character set defined in
52
+ * > [Section 5 of RFC 4648 RFC4648](https://tools.ietf.org/html/rfc4648#section-5), with all trailing '=' characters omitted
42
53
*/
43
54
struct base64url {
44
55
static const std::array<char , 64 >& data () {
@@ -54,155 +65,205 @@ namespace jwt {
54
65
return fill;
55
66
}
56
67
};
68
+ namespace helper {
69
+ /* *
70
+ * @brief A General purpose base64url alphabet respecting the
71
+ * [URI Case Normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1)
72
+ *
73
+ * This is useful in situations outside of JWT encoding/decoding and is provided as a helper
74
+ */
75
+ struct base64url_percent_encoding {
76
+ static const std::array<char , 64 >& data () {
77
+ static constexpr std::array<char , 64 > data{
78
+ {' A' , ' B' , ' C' , ' D' , ' E' , ' F' , ' G' , ' H' , ' I' , ' J' , ' K' , ' L' , ' M' , ' N' , ' O' , ' P' ,
79
+ ' Q' , ' R' , ' S' , ' T' , ' U' , ' V' , ' W' , ' X' , ' Y' , ' Z' , ' a' , ' b' , ' c' , ' d' , ' e' , ' f' ,
80
+ ' g' , ' h' , ' i' , ' j' , ' k' , ' l' , ' m' , ' n' , ' o' , ' p' , ' q' , ' r' , ' s' , ' t' , ' u' , ' v' ,
81
+ ' w' , ' x' , ' y' , ' z' , ' 0' , ' 1' , ' 2' , ' 3' , ' 4' , ' 5' , ' 6' , ' 7' , ' 8' , ' 9' , ' -' , ' _' }};
82
+ return data;
83
+ }
84
+ static const std::initializer_list<std::string>& fill () {
85
+ static std::initializer_list<std::string> fill{" %3D" , " %3d" };
86
+ return fill;
87
+ }
88
+ };
89
+ } // namespace helper
90
+
91
+ inline uint32_t index (const std::array<char , 64 >& alphabet, char symbol) {
92
+ auto itr = std::find_if (alphabet.cbegin (), alphabet.cend (), [symbol](char c) { return c == symbol; });
93
+ if (itr == alphabet.cend ()) { throw std::runtime_error (" Invalid input: not within alphabet" ); }
94
+
95
+ return std::distance (alphabet.cbegin (), itr);
96
+ }
57
97
} // namespace alphabet
58
98
59
99
/* *
60
- * \brief Alphabet generic methods for working with encoding/decoding the base64 family
100
+ * \brief A collection of fellable functions for working with base64 and base64url
61
101
*/
62
- class base {
63
- public:
64
- template <typename T>
65
- static std::string encode (const std::string& bin) {
66
- return encode (bin, T::data (), T::fill ());
67
- }
68
- template <typename T>
69
- static std::string decode (const std::string& base) {
70
- return decode (base, T::data (), T::fill ());
71
- }
72
- template <typename T>
73
- static std::string pad (const std::string& base) {
74
- return pad (base, T::fill ());
75
- }
76
- template <typename T>
77
- static std::string trim (const std::string& base) {
78
- return trim (base, T::fill ());
79
- }
102
+ namespace base {
80
103
81
- private:
82
- static std::string encode (const std::string& bin, const std::array<char , 64 >& alphabet,
83
- const std::string& fill) {
84
- size_t size = bin.size ();
85
- std::string res;
104
+ namespace details {
105
+ struct padding {
106
+ size_t count = 0 ;
107
+ size_t length = 0 ;
86
108
87
- // clear incomplete bytes
88
- size_t fast_size = size - size % 3 ;
89
- for (size_t i = 0 ; i < fast_size;) {
90
- uint32_t octet_a = static_cast <unsigned char >(bin[i++]);
91
- uint32_t octet_b = static_cast <unsigned char >(bin[i++]);
92
- uint32_t octet_c = static_cast <unsigned char >(bin[i++]);
109
+ padding () = default ;
110
+ padding (size_t count, size_t length) : count(count), length(length) {}
93
111
94
- uint32_t triple = (octet_a << 0x10 ) + (octet_b << 0x08 ) + octet_c;
112
+ padding operator +( const padding& p) { return padding (count + p. count , length + p. length ); }
95
113
96
- res += alphabet[(triple >> 3 * 6 ) & 0x3F ];
97
- res += alphabet[(triple >> 2 * 6 ) & 0x3F ];
98
- res += alphabet[(triple >> 1 * 6 ) & 0x3F ];
99
- res += alphabet[(triple >> 0 * 6 ) & 0x3F ];
100
- }
114
+ friend bool operator ==(const padding& lhs, const padding& rhs) {
115
+ return lhs.count == rhs.count && lhs.length == rhs.length ;
116
+ }
117
+ };
118
+
119
+ inline padding count_padding (const std::string& base, const std::vector<std::string>& fills) {
120
+ for (const auto & fill : fills) {
121
+ if (base.size () < fill.size ()) continue ;
122
+ // Does the end of the input exactly match the fill pattern?
123
+ if (base.substr (base.size () - fill.size ()) == fill) {
124
+ return padding{1 , fill.length ()} +
125
+ count_padding (base.substr (0 , base.size () - fill.size ()), fills);
126
+ }
127
+ }
101
128
102
- if (fast_size == size) return res;
103
-
104
- size_t mod = size % 3 ;
105
-
106
- uint32_t octet_a = fast_size < size ? static_cast <unsigned char >(bin[fast_size++]) : 0 ;
107
- uint32_t octet_b = fast_size < size ? static_cast <unsigned char >(bin[fast_size++]) : 0 ;
108
- uint32_t octet_c = fast_size < size ? static_cast <unsigned char >(bin[fast_size++]) : 0 ;
109
-
110
- uint32_t triple = (octet_a << 0x10 ) + (octet_b << 0x08 ) + octet_c;
111
-
112
- switch (mod) {
113
- case 1 :
114
- res += alphabet[(triple >> 3 * 6 ) & 0x3F ];
115
- res += alphabet[(triple >> 2 * 6 ) & 0x3F ];
116
- res += fill;
117
- res += fill;
118
- break ;
119
- case 2 :
120
- res += alphabet[(triple >> 3 * 6 ) & 0x3F ];
121
- res += alphabet[(triple >> 2 * 6 ) & 0x3F ];
122
- res += alphabet[(triple >> 1 * 6 ) & 0x3F ];
123
- res += fill;
124
- break ;
125
- default : break ;
129
+ return {};
126
130
}
127
131
128
- return res;
129
- }
132
+ inline std::string encode (const std::string& bin, const std::array<char , 64 >& alphabet,
133
+ const std::string& fill) {
134
+ size_t size = bin.size ();
135
+ std::string res;
136
+
137
+ // clear incomplete bytes
138
+ size_t fast_size = size - size % 3 ;
139
+ for (size_t i = 0 ; i < fast_size;) {
140
+ uint32_t octet_a = static_cast <unsigned char >(bin[i++]);
141
+ uint32_t octet_b = static_cast <unsigned char >(bin[i++]);
142
+ uint32_t octet_c = static_cast <unsigned char >(bin[i++]);
143
+
144
+ uint32_t triple = (octet_a << 0x10 ) + (octet_b << 0x08 ) + octet_c;
145
+
146
+ res += alphabet[(triple >> 3 * 6 ) & 0x3F ];
147
+ res += alphabet[(triple >> 2 * 6 ) & 0x3F ];
148
+ res += alphabet[(triple >> 1 * 6 ) & 0x3F ];
149
+ res += alphabet[(triple >> 0 * 6 ) & 0x3F ];
150
+ }
151
+
152
+ if (fast_size == size) return res;
130
153
131
- static std::string decode (const std::string& base, const std::array<char , 64 >& alphabet,
132
- const std::string& fill) {
133
- size_t size = base.size ();
134
-
135
- size_t fill_cnt = 0 ;
136
- while (size > fill.size ()) {
137
- if (base.substr (size - fill.size (), fill.size ()) == fill) {
138
- fill_cnt++;
139
- size -= fill.size ();
140
- if (fill_cnt > 2 ) throw std::runtime_error (" Invalid input: too much fill" );
141
- } else
154
+ size_t mod = size % 3 ;
155
+
156
+ uint32_t octet_a = fast_size < size ? static_cast <unsigned char >(bin[fast_size++]) : 0 ;
157
+ uint32_t octet_b = fast_size < size ? static_cast <unsigned char >(bin[fast_size++]) : 0 ;
158
+ uint32_t octet_c = fast_size < size ? static_cast <unsigned char >(bin[fast_size++]) : 0 ;
159
+
160
+ uint32_t triple = (octet_a << 0x10 ) + (octet_b << 0x08 ) + octet_c;
161
+
162
+ switch (mod) {
163
+ case 1 :
164
+ res += alphabet[(triple >> 3 * 6 ) & 0x3F ];
165
+ res += alphabet[(triple >> 2 * 6 ) & 0x3F ];
166
+ res += fill;
167
+ res += fill;
142
168
break ;
169
+ case 2 :
170
+ res += alphabet[(triple >> 3 * 6 ) & 0x3F ];
171
+ res += alphabet[(triple >> 2 * 6 ) & 0x3F ];
172
+ res += alphabet[(triple >> 1 * 6 ) & 0x3F ];
173
+ res += fill;
174
+ break ;
175
+ default : break ;
176
+ }
177
+
178
+ return res;
143
179
}
144
180
145
- if ((size + fill_cnt) % 4 != 0 ) throw std::runtime_error (" Invalid input: incorrect total size" );
181
+ inline std::string decode (const std::string& base, const std::array<char , 64 >& alphabet,
182
+ const std::vector<std::string>& fill) {
183
+ const auto pad = count_padding (base, fill);
184
+ if (pad.count > 2 ) throw std::runtime_error (" Invalid input: too much fill" );
146
185
147
- size_t out_size = size / 4 * 3 ;
148
- std::string res;
149
- res.reserve (out_size);
186
+ const size_t size = base.size () - pad.length ;
187
+ if ((size + pad.count ) % 4 != 0 ) throw std::runtime_error (" Invalid input: incorrect total size" );
150
188
151
- auto get_sextet = [&](size_t offset) {
152
- for (size_t i = 0 ; i < alphabet.size (); i++) {
153
- if (alphabet[i] == base[offset]) return static_cast <uint32_t >(i);
189
+ size_t out_size = size / 4 * 3 ;
190
+ std::string res;
191
+ res.reserve (out_size);
192
+
193
+ auto get_sextet = [&](size_t offset) { return alphabet::index (alphabet, base[offset]); };
194
+
195
+ size_t fast_size = size - size % 4 ;
196
+ for (size_t i = 0 ; i < fast_size;) {
197
+ uint32_t sextet_a = get_sextet (i++);
198
+ uint32_t sextet_b = get_sextet (i++);
199
+ uint32_t sextet_c = get_sextet (i++);
200
+ uint32_t sextet_d = get_sextet (i++);
201
+
202
+ uint32_t triple =
203
+ (sextet_a << 3 * 6 ) + (sextet_b << 2 * 6 ) + (sextet_c << 1 * 6 ) + (sextet_d << 0 * 6 );
204
+
205
+ res += static_cast <char >((triple >> 2 * 8 ) & 0xFFU );
206
+ res += static_cast <char >((triple >> 1 * 8 ) & 0xFFU );
207
+ res += static_cast <char >((triple >> 0 * 8 ) & 0xFFU );
154
208
}
155
- throw std::runtime_error (" Invalid input: not within alphabet" );
156
- };
157
209
158
- size_t fast_size = size - size % 4 ;
159
- for (size_t i = 0 ; i < fast_size;) {
160
- uint32_t sextet_a = get_sextet (i++);
161
- uint32_t sextet_b = get_sextet (i++);
162
- uint32_t sextet_c = get_sextet (i++);
163
- uint32_t sextet_d = get_sextet (i++);
210
+ if (pad.count == 0 ) return res;
211
+
212
+ uint32_t triple = (get_sextet (fast_size) << 3 * 6 ) + (get_sextet (fast_size + 1 ) << 2 * 6 );
164
213
165
- uint32_t triple = (sextet_a << 3 * 6 ) + (sextet_b << 2 * 6 ) + (sextet_c << 1 * 6 ) + (sextet_d << 0 * 6 );
214
+ switch (pad.count ) {
215
+ case 1 :
216
+ triple |= (get_sextet (fast_size + 2 ) << 1 * 6 );
217
+ res += static_cast <char >((triple >> 2 * 8 ) & 0xFFU );
218
+ res += static_cast <char >((triple >> 1 * 8 ) & 0xFFU );
219
+ break ;
220
+ case 2 : res += static_cast <char >((triple >> 2 * 8 ) & 0xFFU ); break ;
221
+ default : break ;
222
+ }
166
223
167
- res += static_cast <char >((triple >> 2 * 8 ) & 0xFFU );
168
- res += static_cast <char >((triple >> 1 * 8 ) & 0xFFU );
169
- res += static_cast <char >((triple >> 0 * 8 ) & 0xFFU );
224
+ return res;
170
225
}
171
226
172
- if (fill_cnt == 0 ) return res;
227
+ inline std::string decode (const std::string& base, const std::array<char , 64 >& alphabet,
228
+ const std::string& fill) {
229
+ return decode (base, alphabet, std::vector<std::string>{fill});
230
+ }
173
231
174
- uint32_t triple = (get_sextet (fast_size) << 3 * 6 ) + (get_sextet (fast_size + 1 ) << 2 * 6 );
232
+ inline std::string pad (const std::string& base, const std::string& fill) {
233
+ std::string padding;
234
+ switch (base.size () % 4 ) {
235
+ case 1 : padding += fill; JWT_FALLTHROUGH;
236
+ case 2 : padding += fill; JWT_FALLTHROUGH;
237
+ case 3 : padding += fill; JWT_FALLTHROUGH;
238
+ default : break ;
239
+ }
175
240
176
- switch (fill_cnt) {
177
- case 1 :
178
- triple |= (get_sextet (fast_size + 2 ) << 1 * 6 );
179
- res += static_cast <char >((triple >> 2 * 8 ) & 0xFFU );
180
- res += static_cast <char >((triple >> 1 * 8 ) & 0xFFU );
181
- break ;
182
- case 2 : res += static_cast <char >((triple >> 2 * 8 ) & 0xFFU ); break ;
183
- default : break ;
241
+ return base + padding;
184
242
}
185
243
186
- return res;
187
- }
188
-
189
- static std::string pad (const std::string& base, const std::string& fill) {
190
- std::string padding;
191
- switch (base.size () % 4 ) {
192
- case 1 : padding += fill; JWT_FALLTHROUGH;
193
- case 2 : padding += fill; JWT_FALLTHROUGH;
194
- case 3 : padding += fill; JWT_FALLTHROUGH;
195
- default : break ;
244
+ inline std::string trim (const std::string& base, const std::string& fill) {
245
+ auto pos = base.find (fill);
246
+ return base.substr (0 , pos);
196
247
}
248
+ } // namespace details
197
249
198
- return base + padding;
250
+ template <typename T>
251
+ std::string encode (const std::string& bin) {
252
+ return details::encode (bin, T::data (), T::fill ());
199
253
}
200
-
201
- static std::string trim (const std::string& base, const std::string& fill) {
202
- auto pos = base.find (fill);
203
- return base.substr (0 , pos);
254
+ template <typename T>
255
+ std::string decode (const std::string& base) {
256
+ return details::decode (base, T::data (), T::fill ());
257
+ }
258
+ template <typename T>
259
+ std::string pad (const std::string& base) {
260
+ return details::pad (base, T::fill ());
261
+ }
262
+ template <typename T>
263
+ std::string trim (const std::string& base) {
264
+ return details::trim (base, T::fill ());
204
265
}
205
- };
266
+ } // namespace base
206
267
} // namespace jwt
207
268
208
269
#endif
0 commit comments