chromium/chromeos/ash/services/device_sync/cryptauth_key_proof_computer_impl.cc

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

#include "chromeos/ash/services/device_sync/cryptauth_key_proof_computer_impl.h"

#include <vector>

#include "base/containers/span.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/services/device_sync/cryptauth_key.h"
#include "chromeos/ash/services/device_sync/proto/cryptauth_common.pb.h"
#include "crypto/ec_private_key.h"
#include "crypto/ec_signature_creator.h"
#include "crypto/hkdf.h"
#include "crypto/hmac.h"

namespace ash {

namespace device_sync {

namespace {

size_t NumBytesForSymmetricKeyType(cryptauthv2::KeyType key_type) {
  switch (key_type) {
    case (cryptauthv2::KeyType::RAW128):
      return 16u;
    case (cryptauthv2::KeyType::RAW256):
      return 32u;
    default:
      NOTREACHED_IN_MIGRATION();
      return 0u;
  }
}

bool IsValidAsymmetricKey(const CryptAuthKey& key) {
  return key.IsAsymmetricKey() && !key.private_key().empty() &&
         key.type() == cryptauthv2::KeyType::P256;
}

std::string ByteVectorToString(const std::vector<uint8_t>& byte_array) {
  return std::string(byte_array.begin(), byte_array.end());
}

}  // namespace

// static
CryptAuthKeyProofComputerImpl::Factory*
    CryptAuthKeyProofComputerImpl::Factory::test_factory_ = nullptr;

// static
std::unique_ptr<CryptAuthKeyProofComputer>
CryptAuthKeyProofComputerImpl::Factory::Create() {
  if (test_factory_)
    return test_factory_->CreateInstance();

  return base::WrapUnique(new CryptAuthKeyProofComputerImpl());
}

// static
void CryptAuthKeyProofComputerImpl::Factory::SetFactoryForTesting(
    Factory* test_factory) {
  test_factory_ = test_factory;
}

CryptAuthKeyProofComputerImpl::Factory::~Factory() = default;

CryptAuthKeyProofComputerImpl::CryptAuthKeyProofComputerImpl() = default;

CryptAuthKeyProofComputerImpl::~CryptAuthKeyProofComputerImpl() = default;

std::optional<std::string> CryptAuthKeyProofComputerImpl::ComputeKeyProof(
    const CryptAuthKey& key,
    const std::string& payload,
    const std::string& salt,
    const std::optional<std::string>& info) {
  if (key.IsAsymmetricKey())
    return ComputeAsymmetricKeyProof(key, payload, salt);

  DCHECK(info);
  return ComputeSymmetricKeyProof(key, payload, salt, *info);
}

std::optional<std::string>
CryptAuthKeyProofComputerImpl::ComputeSymmetricKeyProof(
    const CryptAuthKey& symmetric_key,
    const std::string& payload,
    const std::string& salt,
    const std::string& info) {
  std::string derived_symmetric_key_material =
      crypto::HkdfSha256(symmetric_key.symmetric_key(), salt, info,
                         NumBytesForSymmetricKeyType(symmetric_key.type()));

  crypto::HMAC hmac(crypto::HMAC::HashAlgorithm::SHA256);
  std::vector<unsigned char> signed_payload(hmac.DigestLength());
  bool success =
      hmac.Init(derived_symmetric_key_material) &&
      hmac.Sign(payload, signed_payload.data(), signed_payload.size());

  if (!success) {
    PA_LOG(ERROR) << "Failed to compute symmetric key proof for key handle "
                  << symmetric_key.handle();
    return std::nullopt;
  }

  return std::string(signed_payload.begin(), signed_payload.end());
}

std::optional<std::string>
CryptAuthKeyProofComputerImpl::ComputeAsymmetricKeyProof(
    const CryptAuthKey& asymmetric_key,
    const std::string& payload,
    const std::string& salt) {
  if (!IsValidAsymmetricKey(asymmetric_key)) {
    PA_LOG(ERROR) << "Failed to compute asymmetric key proof for key handle "
                  << asymmetric_key.handle()
                  << ". Invalid key type: " << asymmetric_key.type();
    return std::nullopt;
  }

  std::unique_ptr<crypto::ECPrivateKey> ec_private_key =
      crypto::ECPrivateKey::CreateFromPrivateKeyInfo(
          base::as_bytes(base::make_span(asymmetric_key.private_key())));
  if (!ec_private_key) {
    PA_LOG(ERROR) << "Failed to compute asymmetric key proof for key handle "
                  << asymmetric_key.handle() << ". "
                  << "Invalid private key material; expect DER-encoded PKCS #8 "
                  << "PrivateKeyInfo format (RFC 5208).";
    return std::nullopt;
  }

  std::unique_ptr<crypto::ECSignatureCreator> ec_signature_creator =
      crypto::ECSignatureCreator::Create(ec_private_key.get());
  if (!ec_signature_creator) {
    PA_LOG(ERROR) << "Failed to compute asymmetric key proof for key handle "
                  << asymmetric_key.handle();
    return std::nullopt;
  }

  std::string to_sign = salt + payload;
  std::vector<uint8_t> key_proof;
  bool success = ec_signature_creator->Sign(
      base::as_bytes(base::make_span(to_sign)), &key_proof);
  if (!success) {
    PA_LOG(ERROR) << "Failed to compute asymmetric key proof for key handle "
                  << asymmetric_key.handle();
    return std::nullopt;
  }

  return ByteVectorToString(key_proof);
}

}  // namespace device_sync

}  // namespace ash