1 #include "../filezilla.h"
2
3 #include "digest.h"
4
5 #include "../logging_private.h"
6
7 #include <libfilezilla/encode.hpp>
8 #include <libfilezilla/format.hpp>
9 #include <libfilezilla/hash.hpp>
10 #include <libfilezilla/uri.hpp>
11 #include <libfilezilla/util.hpp>
12
13 namespace {
skipwscomma(char const * & p)14 void skipwscomma(char const*& p)
15 {
16 while (*p && (*p == ' ' || *p == ',')) {
17 ++p;
18 }
19 }
20
unquote(char const * p,char const * end)21 std::string unquote(char const* p, char const* end) {
22
23 if (end - p > 2 && *p == '"') {
24 std::string ret;
25
26 ++p;
27 --end;
28
29 ret.reserve(end - p);
30
31 bool escaped = false;
32 while (p != end) {
33 if (escaped) {
34 ret.push_back(*p);
35 escaped = false;
36 }
37 else if (*p == '\\') {
38 escaped = true;
39 }
40 else {
41 ret.push_back(*p);
42 }
43 ++p;
44 }
45 return ret;
46 }
47 else {
48 return std::string(p, end);
49 }
50 }
51
52
getNext(char const * & p,char const * & sep)53 char const* getNext(char const*&p, char const*& sep)
54 {
55 sep = 0;
56
57 skipwscomma(p);
58
59 if (!*p) {
60 return nullptr;
61 }
62
63 char const* start = p;
64 ++p;
65
66 while (*p) {
67 if (*p == '=') {
68 sep = p;
69 ++p;
70 if (*p == '"') {
71 // quoted-string part of auth-param
72 ++p;
73 bool escaped = false;
74 while (*p) {
75 if (*p == '"') {
76 if (!escaped) {
77 ++p;
78 if (*p && *p != ',' && *p != ' ') {
79 return nullptr;
80 }
81 return start;
82 }
83 }
84 else {
85 if (escaped) {
86 escaped = false;
87 }
88 else if (*p == '\\') {
89 escaped = true;
90 }
91 }
92 ++p;
93 }
94
95 return nullptr;
96 }
97 else {
98 // token86 or token
99 bool t86 = true;
100 while (*p && *p != ',' && *p != ' ') {
101 if (*p != '=') {
102 t86 = false;
103 }
104 ++p;
105 }
106 if (t86) {
107 sep = nullptr;
108 }
109 return start;
110 }
111 }
112 else if (*p == ' ' || *p == ',') {
113 // token86, or next scheme. Caller decides
114 return start;
115 }
116 ++p;
117 }
118
119 // token86, or next scheme. Caller decides
120 return start;
121 }
122 }
123
ParseAuthChallenges(std::string const & header)124 HttpAuthChallenges ParseAuthChallenges(std::string const& header)
125 {
126 // See RFC 7235 how to parse this terrible header.
127
128 HttpAuthChallenges ret;
129
130 char const* p = header.c_str();
131 char const* next_scheme = 0;
132 while (*p) {
133
134 char const* scheme_start;
135 if (next_scheme) {
136 scheme_start = next_scheme;
137 next_scheme = 0;
138 }
139 else {
140 // Extract the scheme
141 skipwscomma(p);
142 scheme_start = p;
143 while (*p && *p != ' ') {
144 ++p;
145 }
146
147 if (!*p || scheme_start == p) {
148 return ret;
149 }
150 }
151 auto const scheme_end = p;
152
153 // Now extract auth params
154
155 HttpAuthParams params;
156
157 while (*p) {
158 char const* sep = 0;
159 auto start = getNext(p, sep);
160 if (!start) {
161 break;
162 }
163
164 if (!sep) {
165 if (params.empty()) {
166 // token86
167 params[""] = std::string(start, p);
168 }
169 else {
170 // It's the next scheme
171 next_scheme = start;
172 }
173 break;
174 }
175 else {
176 params[std::string(start, sep)] = unquote(sep + 1, p);
177 }
178 }
179
180 if (params.empty()) {
181 return ret;
182 }
183
184 ret[std::string(scheme_start, scheme_end)] = params;
185 }
186
187 return ret;
188 }
189
190 namespace {
quote(std::string const & in)191 std::string quote(std::string const& in)
192 {
193 return "\"" + fz::replaced_substrings(fz::replaced_substrings(in, "\\", "\\\\"), "\"", "\\\"") + "\"";
194 }
195
196 template<typename T, typename K>
get(T const & t,K && key)197 typename T::mapped_type get(T const& t, K && key)
198 {
199 auto it = t.find(std::forward<K>(key));
200 if (it != t.cend()) {
201 return it->second;
202 }
203 return typename T::mapped_type();
204 }
205 }
206
BuildDigestAuthorization(HttpAuthParams const & params,unsigned int & nonceCounter,std::string const & verb,fz::uri const & uri,std::string const & user,Credentials const & credentials,fz::logger_interface & logger)207 std::string BuildDigestAuthorization(HttpAuthParams const& params, unsigned int & nonceCounter, std::string const& verb, fz::uri const& uri, std::string const& user, Credentials const& credentials, fz::logger_interface & logger)
208 {
209 // See RFC 7616
210
211 std::string auth = "Digest username=";
212
213 auth += quote(user);
214
215 std::string const opaque = get(params, "opaque");
216 std::string const nonce = get(params, "nonce");
217 std::string const realm = get(params, "realm");
218
219 auth += ", realm=" + quote(realm);
220 auth += ", nonce=" + quote(nonce);
221
222 if (!opaque.empty()) {
223 auth += ", opaque=" + quote(opaque);
224 }
225 auth += ", uri=" + quote(uri.to_string());
226
227 std::string fullAlgorithm = get(params, "algorithm");
228 if (fullAlgorithm.empty()) {
229 fullAlgorithm = "MD5";
230 }
231 auth += ", algorithm=" + fullAlgorithm;
232
233 unsigned int nc = nonceCounter++;
234 auth += ", nc=" + fz::sprintf("%x", nc);
235
236
237 bool sess = false;
238 auto algo = fz::str_toupper_ascii(fullAlgorithm);
239 if (algo.size() > 5 && algo.substr(algo.size() - 5) == "-sess") {
240 sess = true;
241 algo = algo.substr(0, algo.size() - 5);
242 }
243
244 std::vector<uint8_t> (*h)(std::string_view const&) = 0;
245 if (algo == "MD5") {
246 h = &fz::md5;
247 }
248 else if (algo == "SHA-256") {
249 h = &fz::sha256;
250 }
251 else {
252 logger.log(logmsg::error, _("Server requested unsupported digest authentication algorithm: %s"), fullAlgorithm);
253 return std::string();
254 }
255
256 bool qop = false;
257 auto qops = fz::strtok(get(params, "qop"), ",");
258 if (!qops.empty()) {
259 if (std::find(qops.cbegin(), qops.cend(), "auth") == qops.cend()) {
260 logger.log(logmsg::error, _("Server requested unsupported quality-of-protection: %s"), get(params, "qop"));
261 return std::string();
262 }
263 qop = true;
264 }
265
266 auto bytes = fz::random_bytes(16);
267 std::string const cnonce = fz::base64_encode(std::string(bytes.cbegin(), bytes.cend()));
268 auth += ", cnonce=" + quote(cnonce);
269
270 std::string a1 = fz::hex_encode<std::string>(h(user + ":" + realm + ":" + fz::to_utf8(credentials.GetPass())));
271 std::string ha2 = fz::hex_encode<std::string>(h(verb + ":" + uri.to_string()));
272
273 std::string response;
274 if (sess) {
275 a1 = fz::hex_encode<std::string>(h(a1 + ":" + nonce + ":" + cnonce));
276 }
277
278 if (qop) {
279 auth += ", qop=auth";
280 response = fz::hex_encode<std::string>(h(a1 + ":" + nonce + ":" + fz::sprintf("%x", nc) + ":" + cnonce + ":auth:" + ha2));
281 }
282 else {
283 response = fz::hex_encode<std::string>(h(a1 + ":" + nonce + ":" + ha2));
284 }
285
286 auth += ", response=" + quote(response);
287
288 return auth;
289 }
290