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