// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/certificate_transparency/log_proof_fetcher.h" #include #include "base/callback_helpers.h" #include "base/format_macros.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/numerics/safe_conversions.h" #include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/values.h" #include "components/safe_json/safe_json_parser.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/base/request_priority.h" #include "net/cert/ct_log_response_parser.h" #include "net/cert/signed_tree_head.h" #include "net/http/http_status_code.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" #include "url/gurl.h" namespace certificate_transparency { namespace { // Class for issuing a particular request from a CT log and assembling the // response. // Creates the URLRequest instance for fetching the URL from the log // (supplied as |request_url| in the c'tor) and implements the // URLRequest::Delegate interface for assembling the response. class LogFetcher : public net::URLRequest::Delegate { public: using FailureCallback = base::Callback; LogFetcher(net::URLRequestContext* request_context, const GURL& request_url, const base::Closure& success_callback, const FailureCallback& failure_callback); ~LogFetcher() override {} // net::URLRequest::Delegate void OnResponseStarted(net::URLRequest* request) override; void OnReadCompleted(net::URLRequest* request, int bytes_read) override; const std::string& assembled_response() const { return assembled_response_; } private: // Handles the final result of a URLRequest::Read call on the request. // Returns true if another read should be started, false if the read // failed completely or we have to wait for OnResponseStarted to // be called. bool HandleReadResult(int bytes_read); // Calls URLRequest::Read on |request| repeatedly, until HandleReadResult // indicates it should no longer be called. Usually this would be when there // is pending IO that requires waiting for OnResponseStarted to be called. void StartNextReadLoop(); // Invokes the success callback. After this method is called, the LogFetcher // is deleted and no longer safe to call. void RequestComplete(); // Invokes the failure callback with the supplied error information. // After this method the LogFetcher is deleted and no longer safe to call. void InvokeFailureCallback(int net_error, int http_response_code); scoped_ptr url_request_; const GURL request_url_; base::Closure success_callback_; FailureCallback failure_callback_; scoped_refptr response_buffer_; std::string assembled_response_; DISALLOW_COPY_AND_ASSIGN(LogFetcher); }; LogFetcher::LogFetcher(net::URLRequestContext* request_context, const GURL& request_url, const base::Closure& success_callback, const FailureCallback& failure_callback) : request_url_(request_url), success_callback_(success_callback), failure_callback_(failure_callback) { DCHECK(request_url_.SchemeIsHTTPOrHTTPS()); url_request_ = request_context->CreateRequest(request_url_, net::DEFAULT_PRIORITY, this); // This request should not send any cookies or otherwise identifying data, // as CT logs are expected to be publicly-accessible and connections to them // stateless. url_request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_AUTH_DATA); url_request_->Start(); } void LogFetcher::OnResponseStarted(net::URLRequest* request) { DCHECK_EQ(url_request_.get(), request); int http_response_code = request->GetResponseCode(); if (!request->status().is_success()) { InvokeFailureCallback(request->status().error(), http_response_code); return; } if (http_response_code != net::HTTP_OK) { InvokeFailureCallback(net::OK, http_response_code); return; } // Lazily initialize |response_buffer_| to avoid consuming memory until an // actual response has been received. if (!response_buffer_) { response_buffer_ = new net::IOBufferWithSize(LogProofFetcher::kMaxLogResponseSizeInBytes); } StartNextReadLoop(); } void LogFetcher::OnReadCompleted(net::URLRequest* request, int bytes_read) { DCHECK_EQ(url_request_.get(), request); if (HandleReadResult(bytes_read)) StartNextReadLoop(); } bool LogFetcher::HandleReadResult(int bytes_read) { // Start by checking for an error condition. // If there are errors, invoke the failure callback and clean up the // request. if (!url_request_->status().is_success() || bytes_read < 0) { int net_error = url_request_->status().error(); if (net_error == net::OK) net_error = net::URLRequestStatus::FAILED; InvokeFailureCallback(net_error, net::HTTP_OK); return false; } // Not an error, but no data available, so wait for OnReadCompleted // callback. if (url_request_->status().is_io_pending()) return false; // Nothing more to read from the stream - finish handling the response. if (bytes_read == 0) { RequestComplete(); return false; } // Data is available, collect it and indicate another read is needed. DCHECK_GE(bytes_read, 0); // |bytes_read| is non-negative at this point, casting to size_t should be // safe. if (base::checked_cast(bytes_read) > LogProofFetcher::kMaxLogResponseSizeInBytes || LogProofFetcher::kMaxLogResponseSizeInBytes < (assembled_response_.size() + bytes_read)) { // Log response is too big, invoke the failure callback. InvokeFailureCallback(net::ERR_FILE_TOO_BIG, net::HTTP_OK); return false; } assembled_response_.append(response_buffer_->data(), bytes_read); return true; } void LogFetcher::StartNextReadLoop() { bool continue_reading = true; while (continue_reading) { int read_bytes = 0; url_request_->Read(response_buffer_.get(), response_buffer_->size(), &read_bytes); continue_reading = HandleReadResult(read_bytes); } } void LogFetcher::RequestComplete() { // Get rid of the buffer as it really isn't necessary. response_buffer_ = nullptr; base::ResetAndReturn(&success_callback_).Run(); // NOTE: |this| is not valid after invoking the callback, as the LogFetcher // instance will be deleted by the callback. } void LogFetcher::InvokeFailureCallback(int net_error, int http_response_code) { base::ResetAndReturn(&failure_callback_).Run(net_error, http_response_code); // NOTE: |this| is not valid after this callback, as the LogFetcher instance // invoking the callback will be deleted by the callback. } } // namespace // Interface for handling the response from a CT log for a particular // request. // All log responses are JSON and should be parsed; however the response // to each request should be parsed and validated differently. // // LogResponseHandler instances should be deleted by the |done_callback| when // it is invoked. class LogResponseHandler { public: using DoneCallback = base::Callback; // |log_id| will be passed to the |failure_callback| to indicate which log // failures pretain to. LogResponseHandler( const std::string& log_id, const LogProofFetcher::FetchFailedCallback& failure_callback); virtual ~LogResponseHandler(); // Starts the actual fetching from the URL, storing |done_callback| for // invocation when fetching and parsing of the request finished. // It is safe, and expected, to delete this object in the |done_callback|. void StartFetch(net::URLRequestContext* request_context, const GURL& request_url, const DoneCallback& done_callback); // Handle successful fetch by the LogFetcher (by parsing the JSON and // handing the parsed JSON to HandleParsedJson, which is request-specific). void HandleFetchCompletion(); // Handle network failure to complete the request to the log, by invoking // the |done_callback_|. virtual void HandleNetFailure(int net_error, int http_response_code); protected: // Handle successful parsing of JSON by invoking HandleParsedJson, then // invoking the |done_callback_| with the returned Closure. void OnJsonParseSuccess(scoped_ptr parsed_json); // Handle failure to parse the JSON by invoking HandleJsonParseFailure, then // invoking the |done_callback_| with the returned Closure. void OnJsonParseError(const std::string& error); // Handle respones JSON that parsed successfully, usually by // returning the success callback bound to parsed values as a Closure. virtual base::Closure HandleParsedJson(const base::Value& parsed_json) = 0; // Handle failure to parse response JSON, usually by returning the failure // callback bound to a request-specific net error code. virtual base::Closure HandleJsonParseFailure( const std::string& json_error) = 0; const std::string log_id_; LogProofFetcher::FetchFailedCallback failure_callback_; scoped_ptr fetcher_; DoneCallback done_callback_; base::WeakPtrFactory weak_factory_; }; LogResponseHandler::LogResponseHandler( const std::string& log_id, const LogProofFetcher::FetchFailedCallback& failure_callback) : log_id_(log_id), failure_callback_(failure_callback), fetcher_(nullptr), weak_factory_(this) { DCHECK(!failure_callback_.is_null()); } LogResponseHandler::~LogResponseHandler() {} void LogResponseHandler::StartFetch(net::URLRequestContext* request_context, const GURL& request_url, const DoneCallback& done_callback) { done_callback_ = done_callback; fetcher_.reset( new LogFetcher(request_context, request_url, base::Bind(&LogResponseHandler::HandleFetchCompletion, weak_factory_.GetWeakPtr()), base::Bind(&LogResponseHandler::HandleNetFailure, weak_factory_.GetWeakPtr()))); } void LogResponseHandler::HandleFetchCompletion() { safe_json::SafeJsonParser::Parse( fetcher_->assembled_response(), base::Bind(&LogResponseHandler::OnJsonParseSuccess, weak_factory_.GetWeakPtr()), base::Bind(&LogResponseHandler::OnJsonParseError, weak_factory_.GetWeakPtr())); // The assembled_response string is copied into the SafeJsonParser so it // is safe to get rid of the object that owns it. fetcher_.reset(); } void LogResponseHandler::HandleNetFailure(int net_error, int http_response_code) { fetcher_.reset(); LogProofFetcher::FetchFailedCallback failure_callback = base::ResetAndReturn(&failure_callback_); base::ResetAndReturn(&done_callback_) .Run( base::Bind(failure_callback, log_id_, net_error, http_response_code)); // NOTE: |this| is not valid after the |done_callback_| is invoked. } void LogResponseHandler::OnJsonParseSuccess( scoped_ptr parsed_json) { base::ResetAndReturn(&done_callback_).Run(HandleParsedJson(*parsed_json)); // NOTE: |this| is not valid after the |done_callback_| is invoked. } void LogResponseHandler::OnJsonParseError(const std::string& error) { base::ResetAndReturn(&done_callback_).Run(HandleJsonParseFailure(error)); // NOTE: |this| is not valid after the |done_callback_| is invoked. } class GetSTHLogResponseHandler : public LogResponseHandler { public: GetSTHLogResponseHandler( const std::string& log_id, const LogProofFetcher::SignedTreeHeadFetchedCallback& sth_fetch_callback, const LogProofFetcher::FetchFailedCallback& failure_callback) : LogResponseHandler(log_id, failure_callback), sth_fetched_(sth_fetch_callback) {} // Parses the JSON into a net::ct::SignedTreeHead and, if successful, // invokes the success callback with it. Otherwise, invokes the failure // callback indicating the error that occurred. base::Closure HandleParsedJson(const base::Value& parsed_json) override { net::ct::SignedTreeHead signed_tree_head; if (!net::ct::FillSignedTreeHead(parsed_json, &signed_tree_head)) { return base::Bind(base::ResetAndReturn(&failure_callback_), log_id_, net::ERR_CT_STH_INCOMPLETE, net::HTTP_OK); } return base::Bind(base::ResetAndReturn(&sth_fetched_), log_id_, signed_tree_head); } // Invoke the error callback indicating that STH parsing failed. base::Closure HandleJsonParseFailure(const std::string& json_error) override { return base::Bind(base::ResetAndReturn(&failure_callback_), log_id_, net::ERR_CT_STH_PARSING_FAILED, net::HTTP_OK); } private: LogProofFetcher::SignedTreeHeadFetchedCallback sth_fetched_; }; class GetConsistencyProofLogResponseHandler : public LogResponseHandler { public: GetConsistencyProofLogResponseHandler( const std::string& log_id, const LogProofFetcher::ConsistencyProofFetchedCallback& proof_fetch_callback, const LogProofFetcher::FetchFailedCallback& failure_callback) : LogResponseHandler(log_id, failure_callback), proof_fetched_(proof_fetch_callback) {} // Fills a vector of strings with nodes from the received consistency proof // in |parsed_json|, and, if successful, invokes the success callback with the // vector. Otherwise, invokes the failure callback indicating proof parsing // has failed. base::Closure HandleParsedJson(const base::Value& parsed_json) override { std::vector consistency_proof; if (!net::ct::FillConsistencyProof(parsed_json, &consistency_proof)) { return base::Bind(base::ResetAndReturn(&failure_callback_), log_id_, net::ERR_CT_CONSISTENCY_PROOF_PARSING_FAILED, net::HTTP_OK); } return base::Bind(base::ResetAndReturn(&proof_fetched_), log_id_, consistency_proof); } // Invoke the error callback indicating proof fetching failed. base::Closure HandleJsonParseFailure(const std::string& json_error) override { return base::Bind(base::ResetAndReturn(&failure_callback_), log_id_, net::ERR_CT_CONSISTENCY_PROOF_PARSING_FAILED, net::HTTP_OK); } private: LogProofFetcher::ConsistencyProofFetchedCallback proof_fetched_; }; LogProofFetcher::LogProofFetcher(net::URLRequestContext* request_context) : request_context_(request_context), weak_factory_(this) { DCHECK(request_context); } LogProofFetcher::~LogProofFetcher() { STLDeleteContainerPointers(inflight_fetches_.begin(), inflight_fetches_.end()); } void LogProofFetcher::FetchSignedTreeHead( const GURL& base_log_url, const std::string& log_id, const SignedTreeHeadFetchedCallback& fetched_callback, const FetchFailedCallback& failed_callback) { GURL request_url = base_log_url.Resolve("ct/v1/get-sth"); StartFetch(request_url, new GetSTHLogResponseHandler(log_id, fetched_callback, failed_callback)); } void LogProofFetcher::FetchConsistencyProof( const GURL& base_log_url, const std::string& log_id, uint64_t old_tree_size, uint64_t new_tree_size, const ConsistencyProofFetchedCallback& fetched_callback, const FetchFailedCallback& failed_callback) { GURL request_url = base_log_url.Resolve(base::StringPrintf( "ct/v1/get-sth-consistency?first=%" PRIu64 "&second=%" PRIu64, old_tree_size, new_tree_size)); StartFetch(request_url, new GetConsistencyProofLogResponseHandler( log_id, fetched_callback, failed_callback)); } void LogProofFetcher::StartFetch(const GURL& request_url, LogResponseHandler* log_request) { log_request->StartFetch(request_context_, request_url, base::Bind(&LogProofFetcher::OnFetchDone, weak_factory_.GetWeakPtr(), log_request)); inflight_fetches_.insert(log_request); } void LogProofFetcher::OnFetchDone(LogResponseHandler* log_handler, const base::Closure& requestor_callback) { auto it = inflight_fetches_.find(log_handler); DCHECK(it != inflight_fetches_.end()); delete *it; inflight_fetches_.erase(it); requestor_callback.Run(); } } // namespace certificate_transparency