chromium/chrome/browser/nearby_sharing/certificates/nearby_share_private_certificate.h

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_PRIVATE_CERTIFICATE_H_
#define CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_PRIVATE_CERTIFICATE_H_

#include <memory>
#include <optional>
#include <set>
#include <vector>

#include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/gtest_prod_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/nearby_sharing/certificates/nearby_share_encrypted_metadata_key.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom.h"
#include "third_party/nearby/sharing/proto/encrypted_metadata.pb.h"
#include "third_party/nearby/sharing/proto/rpc_resources.pb.h"

namespace crypto {
class ECPrivateKey;
class SymmetricKey;
}  // namespace crypto

// Stores metadata and crypto keys for the local device. This certificate
// can be converted to a public certificate and sent to select contacts, who
// will then use the certificate for authenticating the local device before
// transferring data. Provides method for signing a payload during
// authentication with a remote device. Provides method for encrypting the
// metadata encryption key, which can then be advertised.
class NearbySharePrivateCertificate {
 public:
  // Inverse operation of ToDictionary(). Returns std::nullopt if the
  // conversion is not successful
  static std::optional<NearbySharePrivateCertificate> FromDictionary(
      const base::Value::Dict& dict);

  // Generates a random EC key pair, secret key, and metadata encryption
  // key. Derives the certificate ID from the secret key. Derives the
  // not-after time from |not_before| and the certificate validity period fixed
  // by the Nearby Share protocol. Visibility cannot be "no one".
  NearbySharePrivateCertificate(
      nearby_share::mojom::Visibility visibility,
      base::Time not_before,
      nearby::sharing::proto::EncryptedMetadata unencrypted_metadata);

  NearbySharePrivateCertificate(
      nearby_share::mojom::Visibility visibility,
      base::Time not_before,
      base::Time not_after,
      std::unique_ptr<crypto::ECPrivateKey> key_pair,
      std::unique_ptr<crypto::SymmetricKey> secret_key,
      std::vector<uint8_t> metadata_encryption_key,
      std::vector<uint8_t> id,
      nearby::sharing::proto::EncryptedMetadata unencrypted_metadata,
      std::set<std::vector<uint8_t>> consumed_salts);

  NearbySharePrivateCertificate(const NearbySharePrivateCertificate& other);
  NearbySharePrivateCertificate& operator=(
      const NearbySharePrivateCertificate& other);
  NearbySharePrivateCertificate(NearbySharePrivateCertificate&& other);
  NearbySharePrivateCertificate& operator=(
      NearbySharePrivateCertificate&& other);

  virtual ~NearbySharePrivateCertificate();

  const std::vector<uint8_t>& id() const { return id_; }
  nearby_share::mojom::Visibility visibility() const { return visibility_; }
  base::Time not_before() const { return not_before_; }
  base::Time not_after() const { return not_after_; }
  const nearby::sharing::proto::EncryptedMetadata& unencrypted_metadata()
      const {
    return unencrypted_metadata_;
  }

  // Encrypts |metadata_encryption_key_| with the |secret_key_|, using a
  // randomly generated 2-byte salt that has not already been consumed. Returns
  // std::nullopt if the encryption failed or if there are no remaining salts.
  // Note: Due to the generation and storage of an unconsumed salt, this method
  // is not thread safe.
  std::optional<NearbyShareEncryptedMetadataKey> EncryptMetadataKey();

  // Signs the input |payload| with the private key from |key_pair_|. Returns
  // std::nullopt if the signing was unsuccessful.
  std::optional<std::vector<uint8_t>> Sign(
      base::span<const uint8_t> payload) const;

  // Creates a hash of the |authentication_token|, using |secret_key_|. The use
  // of HKDF and the output vector size is part of the Nearby Share protocol and
  // conforms with the GmsCore implementation.
  std::vector<uint8_t> HashAuthenticationToken(
      base::span<const uint8_t> authentication_token) const;

  // Converts this private certificate to a public certificate proto that can be
  // shared with select contacts. Returns std::nullopt if the conversion was
  // unsuccessful.
  std::optional<nearby::sharing::proto::PublicCertificate> ToPublicCertificate()
      const;

  // Converts this private certificate to a dictionary value for storage
  // in Prefs.
  base::Value::Dict ToDictionary() const;

  // For testing only.
  base::queue<std::vector<uint8_t>>& next_salts_for_testing() {
    return next_salts_for_testing_;
  }
  std::optional<base::TimeDelta>& offset_for_testing() {
    return offset_for_testing_;
  }

 private:
  // Generates a random 2-byte salt used for encrypting the metadata encryption
  // key. Adds returned salt to |consumed_salts_|. Returns std::nullopt if the
  // maximum number of salts have been exhausted or if an unconsumed salt cannot
  // be found in a fixed number of attempts, though this is highly improbably.
  // Note: This function is not thread safe.
  std::optional<std::vector<uint8_t>> GenerateUnusedSalt();

  // Encrypts |unencrypted_metadata_| with the |metadata_encryption_key_|, using
  // the |secret_key_| as salt.
  std::optional<std::vector<uint8_t>> EncryptMetadata() const;

  // Specifies which contacts can receive the public certificate corresponding
  // to this private certificate.
  nearby_share::mojom::Visibility visibility_;

  // The begin/end times of the certificate's validity period. Note: An offset
  // is not yet applied to these values. To avoid issues with clock skew,
  // offsets should be applied during conversion to a public certificate.
  base::Time not_before_;
  base::Time not_after_;

  // The public/private P-256 key pair used for verification/signing to ensure
  // secret of public certificates. The public key is included in the
  // public certificate, but the private key will never leave the device.
  std::unique_ptr<crypto::ECPrivateKey> key_pair_;

  // A 32-byte AES key used, along with a salt, to encrypt the
  // |metadata_encryption_key_|, after which it can be safely advertised.  Also,
  // used to generate an authentication token hash. Included in the public
  // certificate.
  std::unique_ptr<crypto::SymmetricKey> secret_key_;

  // A 14-byte symmetric key used to encrypt |unencrypted_metadata_|. Not
  // included in public certificate.
  std::vector<uint8_t> metadata_encryption_key_;

  // An ID for the certificate, generated from the secret key.
  std::vector<uint8_t> id_;

  // Unencrypted device metadata. The proto name is misleading; it holds data
  // that will eventually be serialized and encrypted.
  nearby::sharing::proto::EncryptedMetadata unencrypted_metadata_;

  // The set of 2-byte salts already used to encrypt the metadata key.
  std::set<std::vector<uint8_t>> consumed_salts_;

  // For testing only.
  base::queue<std::vector<uint8_t>> next_salts_for_testing_;
  std::optional<base::TimeDelta> offset_for_testing_;

  FRIEND_TEST_ALL_PREFIXES(NearbySharePrivateCertificateTest, ToFromDictionary);
};

#endif  // CHROME_BROWSER_NEARBY_SHARING_CERTIFICATES_NEARBY_SHARE_PRIVATE_CERTIFICATE_H_