// Copyright 2014 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.

#ifndef NET_SSL_CHANNEL_ID_SERVICE_H_
#define NET_SSL_CHANNEL_ID_SERVICE_H_

#include <map>
#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/non_thread_safe.h"
#include "base/time/time.h"
#include "net/base/completion_callback.h"
#include "net/base/net_export.h"
#include "net/ssl/channel_id_store.h"

namespace base {
class TaskRunner;
}

namespace net {

class ChannelIDServiceJob;
class ChannelIDServiceRequest;
class ChannelIDServiceWorker;

// A class for creating and fetching domain bound certs. They are used
// to identify users' machines; their public keys are used as channel IDs in
// http://tools.ietf.org/html/draft-balfanz-tls-channelid-00.
// As a result although certs are set to be invalid after one year, we don't
// actually expire them. Once generated, certs are valid as long as the users
// want. Users can delete existing certs, and new certs will be generated
// automatically.

// Inherits from NonThreadSafe in order to use the function
// |CalledOnValidThread|.
class NET_EXPORT ChannelIDService
    : NON_EXPORTED_BASE(public base::NonThreadSafe) {
 public:
  class NET_EXPORT RequestHandle {
   public:
    RequestHandle();
    ~RequestHandle();

    // Cancel the request.  Does nothing if the request finished or was already
    // cancelled.
    void Cancel();

    bool is_active() const { return request_ != NULL; }

   private:
    friend class ChannelIDService;

    void RequestStarted(ChannelIDService* service,
                        ChannelIDServiceRequest* request,
                        const CompletionCallback& callback);

    void OnRequestComplete(int result);

    ChannelIDService* service_;
    ChannelIDServiceRequest* request_;
    CompletionCallback callback_;
  };

  // Password used on EncryptedPrivateKeyInfo data stored in EC private_key
  // values.  (This is not used to provide any security, but to workaround NSS
  // being unable to import unencrypted PrivateKeyInfo for EC keys.)
  static const char kEPKIPassword[];

  // This object owns |channel_id_store|.  |task_runner| will
  // be used to post certificate generation worker tasks.  The tasks are
  // safe for use with WorkerPool and SequencedWorkerPool::CONTINUE_ON_SHUTDOWN.
  ChannelIDService(
      ChannelIDStore* channel_id_store,
      const scoped_refptr<base::TaskRunner>& task_runner);

  ~ChannelIDService();

  // Returns the domain to be used for |host|.  The domain is the
  // "registry controlled domain", or the "ETLD + 1" where one exists, or
  // the origin otherwise.
  static std::string GetDomainForHost(const std::string& host);

  // Tests whether the system time is within the supported range for
  // certificate generation.  This value is cached when ChannelIDService
  // is created, so if the system time is changed by a huge amount, this may no
  // longer hold.
  bool IsSystemTimeValid() const { return is_system_time_valid_; }

  // Fetches the domain bound cert for the specified host if one exists and
  // creates one otherwise. Returns OK if successful or an error code upon
  // failure.
  //
  // On successful completion, |private_key| stores a DER-encoded
  // PrivateKeyInfo struct, and |cert| stores a DER-encoded certificate.
  // The PrivateKeyInfo is always an ECDSA private key.
  //
  // |callback| must not be null. ERR_IO_PENDING is returned if the operation
  // could not be completed immediately, in which case the result code will
  // be passed to the callback when available.
  //
  // |*out_req| will be initialized with a handle to the async request. This
  // RequestHandle object must be cancelled or destroyed before the
  // ChannelIDService is destroyed.
  int GetOrCreateChannelID(
      const std::string& host,
      std::string* private_key,
      std::string* cert,
      const CompletionCallback& callback,
      RequestHandle* out_req);

  // Fetches the domain bound cert for the specified host if one exists.
  // Returns OK if successful, ERR_FILE_NOT_FOUND if none exists, or an error
  // code upon failure.
  //
  // On successful completion, |private_key| stores a DER-encoded
  // PrivateKeyInfo struct, and |cert| stores a DER-encoded certificate.
  // The PrivateKeyInfo is always an ECDSA private key.
  //
  // |callback| must not be null. ERR_IO_PENDING is returned if the operation
  // could not be completed immediately, in which case the result code will
  // be passed to the callback when available. If an in-flight
  // GetChannelID is pending, and a new GetOrCreateDomainBoundCert
  // request arrives for the same domain, the GetChannelID request will
  // not complete until a new cert is created.
  //
  // |*out_req| will be initialized with a handle to the async request. This
  // RequestHandle object must be cancelled or destroyed before the
  // ChannelIDService is destroyed.
  int GetChannelID(
      const std::string& host,
      std::string* private_key,
      std::string* cert,
      const CompletionCallback& callback,
      RequestHandle* out_req);

  // Returns the backing ChannelIDStore.
  ChannelIDStore* GetChannelIDStore();

  // Public only for unit testing.
  int cert_count();
  uint64 requests() const { return requests_; }
  uint64 cert_store_hits() const { return cert_store_hits_; }
  uint64 inflight_joins() const { return inflight_joins_; }
  uint64 workers_created() const { return workers_created_; }

 private:
  // Cancels the specified request. |req| is the handle stored by
  // GetChannelID(). After a request is canceled, its completion
  // callback will not be called.
  void CancelRequest(ChannelIDServiceRequest* req);

  void GotChannelID(int err,
                    const std::string& server_identifier,
                    base::Time expiration_time,
                    const std::string& key,
                    const std::string& cert);
  void GeneratedChannelID(
      const std::string& server_identifier,
      int error,
      scoped_ptr<ChannelIDStore::ChannelID> channel_id);
  void HandleResult(int error,
                    const std::string& server_identifier,
                    const std::string& private_key,
                    const std::string& cert);

  // Searches for an in-flight request for the same domain. If found,
  // attaches to the request and returns true. Returns false if no in-flight
  // request is found.
  bool JoinToInFlightRequest(const base::TimeTicks& request_start,
                             const std::string& domain,
                             std::string* private_key,
                             std::string* cert,
                             bool create_if_missing,
                             const CompletionCallback& callback,
                             RequestHandle* out_req);

  // Looks for the domain bound cert for |domain| in this service's store.
  // Returns OK if it can be found synchronously, ERR_IO_PENDING if the
  // result cannot be obtained synchronously, or a network error code on
  // failure (including failure to find a domain-bound cert of |domain|).
  int LookupChannelID(const base::TimeTicks& request_start,
                      const std::string& domain,
                      std::string* private_key,
                      std::string* cert,
                      bool create_if_missing,
                      const CompletionCallback& callback,
                      RequestHandle* out_req);

  scoped_ptr<ChannelIDStore> channel_id_store_;
  scoped_refptr<base::TaskRunner> task_runner_;

  // inflight_ maps from a server to an active generation which is taking
  // place.
  std::map<std::string, ChannelIDServiceJob*> inflight_;

  uint64 requests_;
  uint64 cert_store_hits_;
  uint64 inflight_joins_;
  uint64 workers_created_;

  bool is_system_time_valid_;

  base::WeakPtrFactory<ChannelIDService> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(ChannelIDService);
};

}  // namespace net

#endif  // NET_SSL_CHANNEL_ID_SERVICE_H_