chromium/chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.cc

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

#include "chrome/browser/nearby_sharing/certificates/nearby_share_decrypted_public_certificate.h"

#include <utility>

#include "chrome/browser/nearby_sharing/certificates/common.h"
#include "chrome/browser/nearby_sharing/certificates/constants.h"
#include "chromeos/ash/components/nearby/common/proto/timestamp.pb.h"
#include "components/cross_device/logging/logging.h"
#include "crypto/aead.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/signature_verifier.h"

namespace {

bool IsDataValid(base::Time not_before,
                 base::Time not_after,
                 base::span<const uint8_t> public_key,
                 crypto::SymmetricKey* secret_key,
                 base::span<const uint8_t> id,
                 base::span<const uint8_t> encrypted_metadata,
                 base::span<const uint8_t> metadata_encryption_key_tag) {
  return not_before < not_after && !public_key.empty() && secret_key &&
         secret_key->key().size() == kNearbyShareNumBytesSecretKey &&
         id.size() == kNearbyShareNumBytesCertificateId &&
         !encrypted_metadata.empty() &&
         metadata_encryption_key_tag.size() ==
             kNearbyShareNumBytesMetadataEncryptionKeyTag;
}

// Attempts to decrypt |encrypted_metadata_key| using the |secret_key|.
// Return std::nullopt if the decryption was unsuccessful.
std::optional<std::vector<uint8_t>> DecryptMetadataKey(
    const NearbyShareEncryptedMetadataKey& encrypted_metadata_key,
    const crypto::SymmetricKey* secret_key) {
  std::unique_ptr<crypto::Encryptor> encryptor =
      CreateNearbyShareCtrEncryptor(secret_key, encrypted_metadata_key.salt());
  if (!encryptor) {
    CD_LOG(ERROR, Feature::NS)
        << "Cannot decrypt metadata key: Could not create CTR encryptor.";
    return std::nullopt;
  }

  std::vector<uint8_t> decrypted_metadata_key;
  if (!encryptor->Decrypt(base::as_bytes(base::make_span(
                              encrypted_metadata_key.encrypted_key())),
                          &decrypted_metadata_key)) {
    return std::nullopt;
  }

  return decrypted_metadata_key;
}

// Attempts to decrypt |encrypted_metadata| with |metadata_encryption_key|,
// using |authentication_key| as the IV. Returns std::nullopt if the decryption
// was unsuccessful.
std::optional<std::vector<uint8_t>> DecryptMetadataPayload(
    base::span<const uint8_t> encrypted_metadata,
    base::span<const uint8_t> metadata_encryption_key,
    const crypto::SymmetricKey* secret_key) {
  // Init() keeps a reference to the input key, so that reference must outlive
  // the lifetime of |aead|.
  std::vector<uint8_t> derived_key = DeriveNearbyShareKey(
      metadata_encryption_key, kNearbyShareNumBytesAesGcmKey);

  crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM);
  aead.Init(derived_key);

  return aead.Open(
      encrypted_metadata,
      /*nonce=*/
      DeriveNearbyShareKey(base::as_bytes(base::make_span(secret_key->key())),
                           kNearbyShareNumBytesAesGcmIv),
      /*additional_data=*/base::span<const uint8_t>());
}

// Returns true if the HMAC of |decrypted_metadata_key| is
// |metadata_encryption_key_tag|.
bool VerifyMetadataEncryptionKeyTag(
    base::span<const uint8_t> decrypted_metadata_key,
    base::span<const uint8_t> metadata_encryption_key_tag) {
  // This array of 0x00 is used to conform with the GmsCore implementation.
  std::vector<uint8_t> key(kNearbyShareNumBytesMetadataEncryptionKeyTag, 0x00);

  std::vector<uint8_t> result(kNearbyShareNumBytesMetadataEncryptionKeyTag);
  crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
  return hmac.Init(key) &&
         hmac.Verify(decrypted_metadata_key, metadata_encryption_key_tag);
}

}  // namespace

// static
std::optional<NearbyShareDecryptedPublicCertificate>
NearbyShareDecryptedPublicCertificate::DecryptPublicCertificate(
    const nearby::sharing::proto::PublicCertificate& public_certificate,
    const NearbyShareEncryptedMetadataKey& encrypted_metadata_key) {
  // Note: The PublicCertificate.metadata_encryption_key and
  // PublicCertificate.for_selected_contacts are not returned from the server
  // for remote devices.
  base::Time not_before = base::Time::FromSecondsSinceUnixEpoch(
      public_certificate.start_time().seconds());
  base::Time not_after = base::Time::FromSecondsSinceUnixEpoch(
      public_certificate.end_time().seconds());
  std::vector<uint8_t> public_key(public_certificate.public_key().begin(),
                                  public_certificate.public_key().end());
  std::unique_ptr<crypto::SymmetricKey> secret_key =
      crypto::SymmetricKey::Import(crypto::SymmetricKey::Algorithm::AES,
                                   public_certificate.secret_key());
  std::vector<uint8_t> id(public_certificate.secret_id().begin(),
                          public_certificate.secret_id().end());
  std::vector<uint8_t> encrypted_metadata(
      public_certificate.encrypted_metadata_bytes().begin(),
      public_certificate.encrypted_metadata_bytes().end());
  std::vector<uint8_t> metadata_encryption_key_tag(
      public_certificate.metadata_encryption_key_tag().begin(),
      public_certificate.metadata_encryption_key_tag().end());

  if (!IsDataValid(not_before, not_after, public_key, secret_key.get(), id,
                   encrypted_metadata, metadata_encryption_key_tag)) {
    return std::nullopt;
  }

  // Note: Failure to decrypt the metadata key or failure to confirm that the
  // decrypted metadata key agrees with the key commitment tag should not log an
  // error. When another device advertises their encrypted metadata key, we do
  // not know what public certificate that corresponds to. So, we will
  // potentially be calling DecryptPublicCertificate() on all of our public
  // certificates with the same encrypted metadata key until we find the correct
  // one.
  std::optional<std::vector<uint8_t>> decrypted_metadata_key =
      DecryptMetadataKey(encrypted_metadata_key, secret_key.get());
  if (!decrypted_metadata_key ||
      !VerifyMetadataEncryptionKeyTag(*decrypted_metadata_key,
                                      metadata_encryption_key_tag)) {
    return std::nullopt;
  }

  // If the key was able to be decrypted, we expect the metadata to be able to
  // be decrypted.
  std::optional<std::vector<uint8_t>> decrypted_metadata_bytes =
      DecryptMetadataPayload(encrypted_metadata, *decrypted_metadata_key,
                             secret_key.get());
  if (!decrypted_metadata_bytes) {
    CD_LOG(ERROR, Feature::NS)
        << "Metadata decryption failed: Failed to decrypt metadata "
        << "payload.";
    return std::nullopt;
  }

  nearby::sharing::proto::EncryptedMetadata unencrypted_metadata;
  if (!unencrypted_metadata.ParseFromArray(decrypted_metadata_bytes->data(),
                                           decrypted_metadata_bytes->size())) {
    CD_LOG(ERROR, Feature::NS)
        << "Metadata decryption failed: Failed to parse decrypted "
        << "metadata payload.";
    return std::nullopt;
  }

  return NearbyShareDecryptedPublicCertificate(
      not_before, not_after, std::move(secret_key), std::move(public_key),
      std::move(id), std::move(unencrypted_metadata),
      public_certificate.for_self_share());
}

NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
    base::Time not_before,
    base::Time not_after,
    std::unique_ptr<crypto::SymmetricKey> secret_key,
    std::vector<uint8_t> public_key,
    std::vector<uint8_t> id,
    nearby::sharing::proto::EncryptedMetadata unencrypted_metadata,
    bool for_self_share)
    : not_before_(not_before),
      not_after_(not_after),
      secret_key_(std::move(secret_key)),
      public_key_(std::move(public_key)),
      id_(std::move(id)),
      unencrypted_metadata_(std::move(unencrypted_metadata)),
      for_self_share_(for_self_share) {}

NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
    const NearbyShareDecryptedPublicCertificate& other) {
  *this = other;
}

NearbyShareDecryptedPublicCertificate&
NearbyShareDecryptedPublicCertificate::operator=(
    const NearbyShareDecryptedPublicCertificate& other) {
  if (this == &other)
    return *this;

  not_before_ = other.not_before_;
  not_after_ = other.not_after_;
  secret_key_ = crypto::SymmetricKey::Import(
      crypto::SymmetricKey::Algorithm::AES, other.secret_key_->key());
  public_key_ = other.public_key_;
  id_ = other.id_;
  unencrypted_metadata_ = other.unencrypted_metadata_;
  for_self_share_ = other.for_self_share_;
  return *this;
}

NearbyShareDecryptedPublicCertificate::NearbyShareDecryptedPublicCertificate(
    NearbyShareDecryptedPublicCertificate&&) = default;

NearbyShareDecryptedPublicCertificate&
NearbyShareDecryptedPublicCertificate::operator=(
    NearbyShareDecryptedPublicCertificate&&) = default;

NearbyShareDecryptedPublicCertificate::
    ~NearbyShareDecryptedPublicCertificate() = default;

bool NearbyShareDecryptedPublicCertificate::VerifySignature(
    base::span<const uint8_t> payload,
    base::span<const uint8_t> signature) const {
  crypto::SignatureVerifier verifier;
  if (!verifier.VerifyInit(crypto::SignatureVerifier::ECDSA_SHA256, signature,
                           public_key_)) {
    CD_LOG(ERROR, Feature::NS)
        << "Verification failed: Initialization unsuccessful.";
    return false;
  }

  verifier.VerifyUpdate(payload);

  return verifier.VerifyFinal();
}

std::vector<uint8_t>
NearbyShareDecryptedPublicCertificate::HashAuthenticationToken(
    base::span<const uint8_t> authentication_token) const {
  return ComputeAuthenticationTokenHash(
      authentication_token,
      base::as_bytes(base::make_span(secret_key_->key())));
}