blob: c6edf116dffa596233a38555a666322e18d03210 [file] [log] [blame]
[email protected]4e5ae20f2010-09-24 04:52:111// Copyright (c) 2010 The Chromium Authors. All rights reserved.
license.botbf09a502008-08-24 00:55:552// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit09911bf2008-07-26 23:55:294//
5// Parse the data returned from the SafeBrowsing v2.1 protocol response.
6
[email protected]39a749c2011-01-28 02:40:467#include <stdlib.h>
8
[email protected]da2f68f2011-05-11 21:40:559#include "base/format_macros.h"
10#include "base/logging.h"
11#include "base/stringprintf.h"
12#include "base/string_split.h"
13#include "build/build_config.h"
[email protected]34b2b002009-11-20 06:53:2814#include "chrome/browser/safe_browsing/protocol_parser.h"
[email protected]f5ce36a2011-01-12 19:24:2115#include "chrome/browser/safe_browsing/safe_browsing_util.h"
[email protected]34b2b002009-11-20 06:53:2816
[email protected]d3ad8b72008-09-17 17:45:0217#if defined(OS_WIN)
18#include <Winsock2.h>
19#elif defined(OS_POSIX)
20#include <arpa/inet.h>
21#endif
initial.commit09911bf2008-07-26 23:55:2922
initial.commit09911bf2008-07-26 23:55:2923namespace {
24// Helper function for quick scans of a line oriented protocol. Note that we use
25// std::string::assign(const charT* s, size_type n)
26// to copy data into 'line'. This form of 'assign' does not call strlen on
27// 'input', which is binary data and is not NULL terminated. 'input' may also
28// contain valid NULL bytes in the payload, which a strlen based copy would
29// truncate.
30bool GetLine(const char* input, int input_len, std::string* line) {
31 const char* pos = input;
32 while (pos && (pos - input < input_len)) {
33 if (*pos == '\n') {
34 line->assign(input, pos - input);
35 return true;
36 }
37 ++pos;
38 }
39 return false;
40}
41}
42
43//------------------------------------------------------------------------------
44// SafeBrowsingParser implementation
45
46SafeBrowsingProtocolParser::SafeBrowsingProtocolParser() {
47}
48
49bool SafeBrowsingProtocolParser::ParseGetHash(
50 const char* chunk_data,
51 int chunk_len,
52 const std::string& key,
53 bool* re_key,
54 std::vector<SBFullHashResult>* full_hashes) {
55 full_hashes->clear();
56 int length = chunk_len;
57 const char* data = chunk_data;
58
59 int offset;
60 std::string line;
61 if (!key.empty()) {
62 if (!GetLine(data, length, &line))
63 return false; // Error! Bad GetHash result.
64
65 if (line == "e:pleaserekey") {
66 *re_key = true;
67 return true;
68 }
69
70 offset = static_cast<int>(line.size()) + 1;
71 data += offset;
72 length -= offset;
73
74 if (!safe_browsing_util::VerifyMAC(key, line, data, length))
75 return false;
76 }
77
78 while (length > 0) {
79 if (!GetLine(data, length, &line))
80 return false;
81
82 offset = static_cast<int>(line.size()) + 1;
83 data += offset;
84 length -= offset;
85
86 std::vector<std::string> cmd_parts;
[email protected]76eb0242010-10-14 00:35:3687 base::SplitString(line, ':', &cmd_parts);
initial.commit09911bf2008-07-26 23:55:2988 if (cmd_parts.size() != 3)
89 return false;
90
91 SBFullHashResult full_hash;
92 full_hash.list_name = cmd_parts[0];
93 full_hash.add_chunk_id = atoi(cmd_parts[1].c_str());
94 int full_hash_len = atoi(cmd_parts[2].c_str());
95
[email protected]3b0f5f62009-01-08 01:22:3096 // Ignore hash results from lists we don't recognize.
97 if (safe_browsing_util::GetListId(full_hash.list_name) < 0) {
98 data += full_hash_len;
99 length -= full_hash_len;
100 continue;
101 }
102
initial.commit09911bf2008-07-26 23:55:29103 while (full_hash_len > 0) {
[email protected]d3ad8b72008-09-17 17:45:02104 DCHECK(static_cast<size_t>(full_hash_len) >= sizeof(SBFullHash));
initial.commit09911bf2008-07-26 23:55:29105 memcpy(&full_hash.hash, data, sizeof(SBFullHash));
106 full_hashes->push_back(full_hash);
107 data += sizeof(SBFullHash);
108 length -= sizeof(SBFullHash);
109 full_hash_len -= sizeof(SBFullHash);
110 }
111 }
112
113 return length == 0;
114}
115
116void SafeBrowsingProtocolParser::FormatGetHash(
117 const std::vector<SBPrefix>& prefixes, std::string* request) {
118 DCHECK(request);
119
120 // Format the request for GetHash.
[email protected]da2f68f2011-05-11 21:40:55121 request->append(base::StringPrintf("%" PRIuS ":%" PRIuS "\n",
122 sizeof(SBPrefix),
123 sizeof(SBPrefix) * prefixes.size()));
initial.commit09911bf2008-07-26 23:55:29124 for (size_t i = 0; i < prefixes.size(); ++i) {
125 request->append(reinterpret_cast<const char*>(&prefixes[i]),
126 sizeof(SBPrefix));
127 }
128}
129
130bool SafeBrowsingProtocolParser::ParseUpdate(
131 const char* chunk_data,
132 int chunk_len,
133 const std::string& key,
134 int* next_update_sec,
135 bool* re_key,
136 bool* reset,
137 std::vector<SBChunkDelete>* deletes,
138 std::vector<ChunkUrl>* chunk_urls) {
139 DCHECK(next_update_sec);
140 DCHECK(deletes);
141 DCHECK(chunk_urls);
142
143 int length = chunk_len;
144 const char* data = chunk_data;
145
146 // Populated below.
147 std::string list_name;
148
149 while (length > 0) {
150 std::string cmd_line;
151 if (!GetLine(data, length, &cmd_line))
152 return false; // Error: bad list format!
153
154 std::vector<std::string> cmd_parts;
[email protected]76eb0242010-10-14 00:35:36155 base::SplitString(cmd_line, ':', &cmd_parts);
initial.commit09911bf2008-07-26 23:55:29156 if (cmd_parts.empty())
157 return false;
158 const std::string& command = cmd_parts[0];
[email protected]22717d1e2008-10-15 21:55:32159 if (cmd_parts.size() != 2 && command[0] != 'u')
initial.commit09911bf2008-07-26 23:55:29160 return false;
161
162 const int consumed = static_cast<int>(cmd_line.size()) + 1;
163 data += consumed;
164 length -= consumed;
165 if (length < 0)
166 return false; // Parsing error.
167
168 // Differentiate on the first character of the command (which is usually
169 // only one character, with the exception of the 'ad' and 'sd' commands).
170 switch (command[0]) {
171 case 'a':
172 case 's': {
173 // Must be either an 'ad' (add-del) or 'sd' (sub-del) chunk. We must
174 // have also parsed the list name before getting here, or the add-del
175 // or sub-del will have no context.
176 if (command.size() != 2 || command[1] != 'd' || list_name.empty())
177 return false;
178 SBChunkDelete chunk_delete;
179 chunk_delete.is_sub_del = command[0] == 's';
180 StringToRanges(cmd_parts[1], &chunk_delete.chunk_del);
181 chunk_delete.list_name = list_name;
182 deletes->push_back(chunk_delete);
183 break;
184 }
185
186 case 'e':
187 if (cmd_parts[1] != "pleaserekey")
188 return false;
189 *re_key = true;
190 break;
191
192 case 'i':
193 // The line providing the name of the list (i.e. 'goog-phish-shavar').
194 list_name = cmd_parts[1];
195 break;
196
197 case 'm':
198 // Verify that the MAC of the remainer of this chunk is what we expect.
199 if (!key.empty() &&
200 !safe_browsing_util::VerifyMAC(key, cmd_parts[1], data, length))
201 return false;
202 break;
203
204 case 'n':
205 // The line providing the next earliest time (in seconds) to re-query.
206 *next_update_sec = atoi(cmd_parts[1].c_str());
207 break;
208
209 case 'u': {
[email protected]22717d1e2008-10-15 21:55:32210 // The redirect command is of the form: u:<url>,<mac> where <url> can
211 // contain multiple colons, commas or any valid URL characters. We scan
212 // backwards in the string looking for the first ',' we encounter and
213 // assume that everything before that is the URL and everything after
214 // is the MAC (if the MAC was requested).
initial.commit09911bf2008-07-26 23:55:29215 std::string mac;
[email protected]22717d1e2008-10-15 21:55:32216 std::string redirect_url(cmd_line, 2); // Skip the initial "u:".
initial.commit09911bf2008-07-26 23:55:29217 if (!key.empty()) {
218 std::string::size_type mac_pos = redirect_url.rfind(',');
219 if (mac_pos == std::string::npos)
220 return false;
221 mac = redirect_url.substr(mac_pos + 1);
222 redirect_url = redirect_url.substr(0, mac_pos);
223 }
[email protected]22717d1e2008-10-15 21:55:32224
initial.commit09911bf2008-07-26 23:55:29225 ChunkUrl chunk_url;
226 chunk_url.url = redirect_url;
[email protected]8b02bb8a2008-10-22 02:05:09227 chunk_url.list_name = list_name;
initial.commit09911bf2008-07-26 23:55:29228 if (!key.empty())
229 chunk_url.mac = mac;
230 chunk_urls->push_back(chunk_url);
231 break;
232 }
233
234 case 'r':
235 if (cmd_parts[1] != "pleasereset")
236 return false;
237 *reset = true;
238 break;
239
240 default:
[email protected]22717d1e2008-10-15 21:55:32241 // According to the spec, we ignore commands we don't understand.
242 break;
initial.commit09911bf2008-07-26 23:55:29243 }
244 }
245
246 return true;
247}
248
[email protected]f5ce36a2011-01-12 19:24:21249bool SafeBrowsingProtocolParser::ParseChunk(const std::string& list_name,
250 const char* data,
initial.commit09911bf2008-07-26 23:55:29251 int length,
252 const std::string& key,
253 const std::string& mac,
254 bool* re_key,
[email protected]7b1e37102010-03-08 21:43:16255 SBChunkList* chunks) {
initial.commit09911bf2008-07-26 23:55:29256 int remaining = length;
257 const char* chunk_data = data;
258
259 if (!key.empty() &&
260 !safe_browsing_util::VerifyMAC(key, mac, data, length)) {
261 return false;
262 }
263
264 while (remaining > 0) {
265 std::string cmd_line;
266 if (!GetLine(chunk_data, length, &cmd_line))
267 return false; // Error: bad chunk format!
268
269 const int line_len = static_cast<int>(cmd_line.length()) + 1;
[email protected]a781b072011-01-10 23:07:23270 chunk_data += line_len;
271 remaining -= line_len;
initial.commit09911bf2008-07-26 23:55:29272 std::vector<std::string> cmd_parts;
[email protected]76eb0242010-10-14 00:35:36273 base::SplitString(cmd_line, ':', &cmd_parts);
initial.commit09911bf2008-07-26 23:55:29274
275 // Handle a possible re-key command.
276 if (cmd_parts.size() != 4) {
277 if (cmd_parts.size() == 2 &&
278 cmd_parts[0] == "e" &&
279 cmd_parts[1] == "pleaserekey") {
280 *re_key = true;
initial.commit09911bf2008-07-26 23:55:29281 continue;
282 }
283 return false;
284 }
285
286 // Process the chunk data.
287 const int chunk_number = atoi(cmd_parts[1].c_str());
288 const int hash_len = atoi(cmd_parts[2].c_str());
289 if (hash_len != sizeof(SBPrefix) && hash_len != sizeof(SBFullHash)) {
[email protected]53b7bf912010-10-25 18:33:19290 VLOG(1) << "ParseChunk got unknown hashlen " << hash_len;
initial.commit09911bf2008-07-26 23:55:29291 return false;
292 }
293
294 const int chunk_len = atoi(cmd_parts[3].c_str());
[email protected]a781b072011-01-10 23:07:23295
296 if (remaining < chunk_len)
297 return false; // parse error.
initial.commit09911bf2008-07-26 23:55:29298
299 chunks->push_back(SBChunk());
300 chunks->back().chunk_number = chunk_number;
301
302 if (cmd_parts[0] == "a") {
[email protected]445d4cc2008-10-09 21:31:57303 chunks->back().is_add = true;
[email protected]f5ce36a2011-01-12 19:24:21304 if (!ParseAddChunk(list_name, chunk_data, chunk_len, hash_len,
[email protected]d3216442009-03-05 21:07:27305 &chunks->back().hosts))
initial.commit09911bf2008-07-26 23:55:29306 return false; // Parse error.
307 } else if (cmd_parts[0] == "s") {
[email protected]445d4cc2008-10-09 21:31:57308 chunks->back().is_add = false;
[email protected]f5ce36a2011-01-12 19:24:21309 if (!ParseSubChunk(list_name, chunk_data, chunk_len, hash_len,
[email protected]d3216442009-03-05 21:07:27310 &chunks->back().hosts))
initial.commit09911bf2008-07-26 23:55:29311 return false; // Parse error.
312 } else {
313 NOTREACHED();
314 return false;
315 }
316
317 chunk_data += chunk_len;
318 remaining -= chunk_len;
[email protected]a781b072011-01-10 23:07:23319 DCHECK_LE(0, remaining);
initial.commit09911bf2008-07-26 23:55:29320 }
321
322 DCHECK(remaining == 0);
323
324 return true;
325}
326
[email protected]f5ce36a2011-01-12 19:24:21327bool SafeBrowsingProtocolParser::ParseAddChunk(const std::string& list_name,
328 const char* data,
329 int data_len,
330 int hash_len,
331 std::deque<SBChunkHost>* hosts) {
initial.commit09911bf2008-07-26 23:55:29332 const char* chunk_data = data;
[email protected]f5ce36a2011-01-12 19:24:21333 int remaining = data_len;
334 int prefix_count;
335 SBEntry::Type type = hash_len == sizeof(SBPrefix) ?
336 SBEntry::ADD_PREFIX : SBEntry::ADD_FULL_HASH;
initial.commit09911bf2008-07-26 23:55:29337
[email protected]f5ce36a2011-01-12 19:24:21338 if (list_name == safe_browsing_util::kBinHashList) {
339 // kBinHashList only contains prefixes, no HOSTKEY and COUNT.
340 DCHECK_EQ(0, remaining % hash_len);
341 prefix_count = remaining / hash_len;
342 SBChunkHost chunk_host;
343 chunk_host.host = 0;
344 chunk_host.entry = SBEntry::Create(type, prefix_count);
345 hosts->push_back(chunk_host);
346 if (!ReadPrefixes(&chunk_data, &remaining, chunk_host.entry, prefix_count))
347 return false;
348 } else {
initial.commit09911bf2008-07-26 23:55:29349 SBPrefix host;
[email protected]f5ce36a2011-01-12 19:24:21350 const int min_size = sizeof(SBPrefix) + 1;
351 while (remaining >= min_size) {
352 ReadHostAndPrefixCount(&chunk_data, &remaining, &host, &prefix_count);
initial.commit09911bf2008-07-26 23:55:29353 SBChunkHost chunk_host;
354 chunk_host.host = host;
[email protected]f5ce36a2011-01-12 19:24:21355 chunk_host.entry = SBEntry::Create(type, prefix_count);
initial.commit09911bf2008-07-26 23:55:29356 hosts->push_back(chunk_host);
[email protected]f5ce36a2011-01-12 19:24:21357 if (!ReadPrefixes(&chunk_data, &remaining, chunk_host.entry,
358 prefix_count))
359 return false;
initial.commit09911bf2008-07-26 23:55:29360 }
initial.commit09911bf2008-07-26 23:55:29361 }
initial.commit09911bf2008-07-26 23:55:29362 return remaining == 0;
363}
364
[email protected]f5ce36a2011-01-12 19:24:21365bool SafeBrowsingProtocolParser::ParseSubChunk(const std::string& list_name,
366 const char* data,
367 int data_len,
368 int hash_len,
369 std::deque<SBChunkHost>* hosts) {
initial.commit09911bf2008-07-26 23:55:29370 int remaining = data_len;
371 const char* chunk_data = data;
[email protected]f5ce36a2011-01-12 19:24:21372 int prefix_count;
373 SBEntry::Type type = hash_len == sizeof(SBPrefix) ?
374 SBEntry::SUB_PREFIX : SBEntry::SUB_FULL_HASH;
initial.commit09911bf2008-07-26 23:55:29375
[email protected]f5ce36a2011-01-12 19:24:21376 if (list_name == safe_browsing_util::kBinHashList) {
initial.commit09911bf2008-07-26 23:55:29377 SBChunkHost chunk_host;
[email protected]f5ce36a2011-01-12 19:24:21378 // Set host to 0 and it won't be used for kBinHashList.
379 chunk_host.host = 0;
380 // kBinHashList only contains (add_chunk_number, prefix) pairs, no HOSTKEY
381 // and COUNT. |add_chunk_number| is int32.
382 prefix_count = remaining / (sizeof(int32) + hash_len);
383 chunk_host.entry = SBEntry::Create(type, prefix_count);
384 if (!ReadPrefixes(&chunk_data, &remaining, chunk_host.entry, prefix_count))
initial.commit09911bf2008-07-26 23:55:29385 return false;
[email protected]f5ce36a2011-01-12 19:24:21386 hosts->push_back(chunk_host);
387 } else {
388 SBPrefix host;
389 const int min_size = 2 * sizeof(SBPrefix) + 1;
390 while (remaining >= min_size) {
391 ReadHostAndPrefixCount(&chunk_data, &remaining, &host, &prefix_count);
392 SBChunkHost chunk_host;
393 chunk_host.host = host;
394 chunk_host.entry = SBEntry::Create(type, prefix_count);
395 hosts->push_back(chunk_host);
396 if (prefix_count == 0) {
397 // There is only an add chunk number (no prefixes).
398 chunk_host.entry->set_chunk_id(ReadChunkId(&chunk_data, &remaining));
399 continue;
400 }
401 if (!ReadPrefixes(&chunk_data, &remaining, chunk_host.entry,
402 prefix_count))
403 return false;
404 }
initial.commit09911bf2008-07-26 23:55:29405 }
initial.commit09911bf2008-07-26 23:55:29406 return remaining == 0;
407}
408
initial.commit09911bf2008-07-26 23:55:29409void SafeBrowsingProtocolParser::ReadHostAndPrefixCount(
410 const char** data, int* remaining, SBPrefix* host, int* count) {
411 // Next 4 bytes are the host prefix.
412 memcpy(host, *data, sizeof(SBPrefix));
413 *data += sizeof(SBPrefix);
414 *remaining -= sizeof(SBPrefix);
415
416 // Next 1 byte is the prefix count (could be zero, but never negative).
417 *count = static_cast<unsigned char>(**data);
418 *data += 1;
419 *remaining -= 1;
420}
421
422int SafeBrowsingProtocolParser::ReadChunkId(
423 const char** data, int* remaining) {
424 int chunk_number;
425 memcpy(&chunk_number, *data, sizeof(chunk_number));
426 *data += sizeof(chunk_number);
427 *remaining -= sizeof(chunk_number);
428 return htonl(chunk_number);
429}
430
431bool SafeBrowsingProtocolParser::ReadPrefixes(
[email protected]f5ce36a2011-01-12 19:24:21432 const char** data, int* remaining, SBEntry* entry, int count) {
initial.commit09911bf2008-07-26 23:55:29433 int hash_len = entry->HashLen();
434 for (int i = 0; i < count; ++i) {
435 if (entry->IsSub()) {
[email protected]f5ce36a2011-01-12 19:24:21436 entry->SetChunkIdAtPrefix(i, ReadChunkId(data, remaining));
initial.commit09911bf2008-07-26 23:55:29437 if (*remaining <= 0)
438 return false;
439 }
440
[email protected]080438b82009-12-03 18:17:12441 if (entry->IsPrefix()) {
[email protected]f5ce36a2011-01-12 19:24:21442 entry->SetPrefixAt(i, *reinterpret_cast<const SBPrefix*>(*data));
initial.commit09911bf2008-07-26 23:55:29443 } else {
[email protected]f5ce36a2011-01-12 19:24:21444 entry->SetFullHashAt(i, *reinterpret_cast<const SBFullHash*>(*data));
initial.commit09911bf2008-07-26 23:55:29445 }
446 *data += hash_len;
447 *remaining -= hash_len;
448 if (*remaining < 0)
449 return false;
450 }
451
452 return true;
453}
454
455bool SafeBrowsingProtocolParser::ParseNewKey(const char* chunk_data,
456 int chunk_length,
457 std::string* client_key,
458 std::string* wrapped_key) {
459 DCHECK(client_key && wrapped_key);
460 client_key->clear();
461 wrapped_key->clear();
462
463 const char* data = chunk_data;
464 int remaining = chunk_length;
465
466 while (remaining > 0) {
467 std::string line;
468 if (!GetLine(data, remaining, &line))
469 return false;
470
471 std::vector<std::string> cmd_parts;
[email protected]76eb0242010-10-14 00:35:36472 base::SplitString(line, ':', &cmd_parts);
initial.commit09911bf2008-07-26 23:55:29473 if (cmd_parts.size() != 3)
474 return false;
475
[email protected]d3ad8b72008-09-17 17:45:02476 if (static_cast<int>(cmd_parts[2].size()) != atoi(cmd_parts[1].c_str()))
initial.commit09911bf2008-07-26 23:55:29477 return false;
478
479 if (cmd_parts[0] == "clientkey") {
480 client_key->assign(cmd_parts[2]);
481 } else if (cmd_parts[0] == "wrappedkey") {
482 wrapped_key->assign(cmd_parts[2]);
483 } else {
484 return false;
485 }
486
487 data += line.size() + 1;
488 remaining -= static_cast<int>(line.size()) + 1;
489 }
490
491 if (client_key->empty() || wrapped_key->empty())
492 return false;
493
494 return true;
license.botbf09a502008-08-24 00:55:55495}