chromium/chromeos/ash/components/cryptohome/auth_factor_conversions.cc

// Copyright 2022 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/components/cryptohome/auth_factor_conversions.h"

#include <optional>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "chromeos/ash/components/cryptohome/auth_factor.h"
#include "chromeos/ash/components/cryptohome/auth_factor_input.h"
#include "chromeos/ash/components/cryptohome/common_types.h"
#include "chromeos/ash/components/dbus/cryptohome/UserDataAuth.pb.h"
#include "chromeos/ash/components/dbus/cryptohome/auth_factor.pb.h"
#include "chromeos/ash/components/dbus/cryptohome/recoverable_key_store.pb.h"

namespace cryptohome {

namespace {

using ::ash::ChallengeResponseKey;

KnowledgeFactorHashAlgorithm ConvertHashTypeToProto(
    KnowledgeFactorHashAlgorithmWrapper algorithm) {
  using Algorithm = KnowledgeFactorHashAlgorithmWrapper;
  switch (algorithm) {
    case Algorithm::kSha256TopHalf:
      return KnowledgeFactorHashAlgorithm::HASH_TYPE_SHA256_TOP_HALF;
    case Algorithm::kPbkdf2Aes2561234:
      return KnowledgeFactorHashAlgorithm::HASH_TYPE_PBKDF2_AES256_1234;
  }
}

void ConvertKnowledgeFactorHashInfoToProto(
    const KnowledgeFactorHashInfo& hash_info,
    user_data_auth::KnowledgeFactorHashInfo& hash_info_proto) {
  hash_info_proto.set_algorithm(ConvertHashTypeToProto(hash_info.algorithm));
  hash_info_proto.set_salt(hash_info.salt);
  hash_info_proto.set_should_generate_key_store(
      hash_info.should_generate_key_store);
}

PinStatus PasrePinFactorStatus(const user_data_auth::StatusInfo& proto) {
  base::TimeDelta available_in = base::TimeDelta::Max();
  if (proto.time_available_in() != std::numeric_limits<uint64_t>::max()) {
    available_in = base::Milliseconds(proto.time_available_in());
  }
  CHECK(!available_in.is_negative());
  return PinStatus{available_in};
}

PasswordMetadata ParsePasswordMetadata(
    const user_data_auth::AuthFactor& proto) {
  std::optional<KnowledgeFactorHashInfo> hash_info;
  if (proto.has_password_metadata() &&
      proto.password_metadata().has_hash_info()) {
    const user_data_auth::KnowledgeFactorHashInfo& hash_info_proto =
        proto.password_metadata().hash_info();
    DCHECK_EQ(hash_info_proto.algorithm(),
              KnowledgeFactorHashAlgorithm::HASH_TYPE_SHA256_TOP_HALF);
    return hash_info_proto.should_generate_key_store()
               ? PasswordMetadata::CreateForLocalPassword(
                     SystemSalt(hash_info_proto.salt()))
               : PasswordMetadata::CreateForOnlinePassword(
                     SystemSalt(hash_info_proto.salt()));
  }
  return PasswordMetadata::CreateWithoutSalt();
}

PinMetadata ParsePinMetadata(const user_data_auth::AuthFactor& proto) {
  std::optional<KnowledgeFactorHashInfo> hash_info;
  if (proto.has_pin_metadata() && proto.pin_metadata().has_hash_info()) {
    const user_data_auth::KnowledgeFactorHashInfo& hash_info_proto =
        proto.pin_metadata().hash_info();
    DCHECK_EQ(hash_info_proto.algorithm(),
              KnowledgeFactorHashAlgorithm::HASH_TYPE_PBKDF2_AES256_1234);
    DCHECK(hash_info_proto.should_generate_key_store());
    return PinMetadata::Create(PinSalt(hash_info_proto.salt()));
  }
  return PinMetadata::CreateWithoutSalt();
}

}  // namespace

user_data_auth::AuthFactorType ConvertFactorTypeToProto(AuthFactorType type) {
  switch (type) {
    case AuthFactorType::kUnknownLegacy:
      NOTREACHED_IN_MIGRATION()
          << "Unknown factor type should never be sent to cryptohome";
      return user_data_auth::AUTH_FACTOR_TYPE_UNSPECIFIED;
    case AuthFactorType::kPassword:
      return user_data_auth::AUTH_FACTOR_TYPE_PASSWORD;
    case AuthFactorType::kPin:
      return user_data_auth::AUTH_FACTOR_TYPE_PIN;
    case AuthFactorType::kRecovery:
      return user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY;
    case AuthFactorType::kKiosk:
      return user_data_auth::AUTH_FACTOR_TYPE_KIOSK;
    case AuthFactorType::kSmartCard:
      return user_data_auth::AUTH_FACTOR_TYPE_SMART_CARD;
    case AuthFactorType::kLegacyFingerprint:
      return user_data_auth::AUTH_FACTOR_TYPE_LEGACY_FINGERPRINT;
    case AuthFactorType::kFingerprint:
      return user_data_auth::AUTH_FACTOR_TYPE_FINGERPRINT;
  }
}

std::optional<AuthFactorType> SafeConvertFactorTypeFromProto(
    user_data_auth::AuthFactorType type) {
  switch (type) {
    case user_data_auth::AUTH_FACTOR_TYPE_UNSPECIFIED:
      LOG(WARNING) << "Unknown factor type should be handled separately";
      return std::nullopt;
    case user_data_auth::AUTH_FACTOR_TYPE_LEGACY_FINGERPRINT:
      LOG(WARNING) << "Fingerprint factor type should never be returned";
      return std::nullopt;
    case user_data_auth::AUTH_FACTOR_TYPE_PASSWORD:
      return AuthFactorType::kPassword;
    case user_data_auth::AUTH_FACTOR_TYPE_PIN:
      return AuthFactorType::kPin;
    case user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY:
      return AuthFactorType::kRecovery;
    case user_data_auth::AUTH_FACTOR_TYPE_KIOSK:
      return AuthFactorType::kKiosk;
    case user_data_auth::AUTH_FACTOR_TYPE_SMART_CARD:
      return AuthFactorType::kSmartCard;
    case user_data_auth::AUTH_FACTOR_TYPE_FINGERPRINT:
      return AuthFactorType::kFingerprint;
    default:
      LOG(WARNING)
          << "Unknown auth factor type " << static_cast<int>(type)
          << " Probably factor was added in cryptohome, but is not supported "
             "in chrome yet.";
      return std::nullopt;
  }
}

AuthFactorType ConvertFactorTypeFromProto(user_data_auth::AuthFactorType type) {
  switch (type) {
    case user_data_auth::AUTH_FACTOR_TYPE_UNSPECIFIED:
      LOG(FATAL) << "Unknown factor type should be handled separately";
    case user_data_auth::AUTH_FACTOR_TYPE_LEGACY_FINGERPRINT:
      LOG(FATAL) << "Fingerprint factor type should never be returned";
    case user_data_auth::AUTH_FACTOR_TYPE_PASSWORD:
      return AuthFactorType::kPassword;
    case user_data_auth::AUTH_FACTOR_TYPE_PIN:
      return AuthFactorType::kPin;
    case user_data_auth::AUTH_FACTOR_TYPE_CRYPTOHOME_RECOVERY:
      return AuthFactorType::kRecovery;
    case user_data_auth::AUTH_FACTOR_TYPE_KIOSK:
      return AuthFactorType::kKiosk;
    case user_data_auth::AUTH_FACTOR_TYPE_SMART_CARD:
      return AuthFactorType::kSmartCard;
    case user_data_auth::AUTH_FACTOR_TYPE_FINGERPRINT:
      return AuthFactorType::kFingerprint;
    default:
      // Use `--ignore-unknown-auth-factors` to avoid this.
      LOG(FATAL) << "Unknown auth factor type " << static_cast<int>(type);
  }
}

user_data_auth::SmartCardSignatureAlgorithm
ChallengeSignatureAlgorithmToProtoEnum(
    ChallengeResponseKey::SignatureAlgorithm algorithm) {
  using Algorithm = ChallengeResponseKey::SignatureAlgorithm;
  switch (algorithm) {
    case Algorithm::kRsassaPkcs1V15Sha1:
      return user_data_auth::CHALLENGE_RSASSA_PKCS1_V1_5_SHA1;
    case Algorithm::kRsassaPkcs1V15Sha256:
      return user_data_auth::CHALLENGE_RSASSA_PKCS1_V1_5_SHA256;
    case Algorithm::kRsassaPkcs1V15Sha384:
      return user_data_auth::CHALLENGE_RSASSA_PKCS1_V1_5_SHA384;
    case Algorithm::kRsassaPkcs1V15Sha512:
      return user_data_auth::CHALLENGE_RSASSA_PKCS1_V1_5_SHA512;
  }
  NOTREACHED_IN_MIGRATION();
}

void SerializeAuthFactor(const AuthFactor& factor,
                         user_data_auth::AuthFactor* out_proto) {
  out_proto->set_type(ConvertFactorTypeToProto(factor.ref().type()));
  out_proto->set_label(factor.ref().label().value());
  // Do not do anything with is_active_for_login yet.

  CHECK_NE(factor.GetCommonMetadata().chrome_version_last_updated().value(),
           kFallbackFactorVersion);
  CHECK_NE(factor.GetCommonMetadata().chromeos_version_last_updated().value(),
           kFallbackFactorVersion);

  out_proto->mutable_common_metadata()->set_chrome_version_last_updated(
      factor.GetCommonMetadata().chrome_version_last_updated().value());
  const auto& chromeos_version =
      factor.GetCommonMetadata().chromeos_version_last_updated().value();
  if (!chromeos_version.empty()) {
    out_proto->mutable_common_metadata()->set_chromeos_version_last_updated(
        chromeos_version);
  }

  switch (factor.ref().type()) {
    case AuthFactorType::kPassword: {
      user_data_auth::PasswordMetadata& password_metadata_proto =
          *out_proto->mutable_password_metadata();
      if (factor.GetPasswordMetadata().hash_info().has_value()) {
        ConvertKnowledgeFactorHashInfoToProto(
            *factor.GetPasswordMetadata().hash_info(),
            *password_metadata_proto.mutable_hash_info());
      }
      break;
    }
    case AuthFactorType::kPin: {
      if (ash::features::IsAllowPinTimeoutSetupEnabled()) {
        out_proto->mutable_common_metadata()->set_lockout_policy(
            user_data_auth::LOCKOUT_POLICY_TIME_LIMITED);
      } else {
        out_proto->mutable_common_metadata()->set_lockout_policy(
            user_data_auth::LOCKOUT_POLICY_ATTEMPT_LIMITED);
      }
      user_data_auth::PinMetadata& pin_metadata_proto =
          *out_proto->mutable_pin_metadata();
      if (factor.GetPinMetadata().hash_info().has_value()) {
        ConvertKnowledgeFactorHashInfoToProto(
            *factor.GetPinMetadata().hash_info(),
            *pin_metadata_proto.mutable_hash_info());
      }
      break;
    }
    case AuthFactorType::kRecovery:
      out_proto->mutable_cryptohome_recovery_metadata()->set_mediator_pub_key(
          factor.GetCryptohomeRecoveryMetadata().mediator_pub_key);
      break;
    case AuthFactorType::kKiosk:
      out_proto->mutable_kiosk_metadata();
      break;
    case AuthFactorType::kSmartCard:
      out_proto->mutable_smart_card_metadata()->set_public_key_spki_der(
          factor.GetSmartCardMetadata().public_key_spki_der);
      break;
    case AuthFactorType::kFingerprint:
      out_proto->mutable_fingerprint_metadata();
      break;
    case AuthFactorType::kLegacyFingerprint:
      LOG(FATAL) << "Legacy fingerprint factor type should never be serialized";
    case AuthFactorType::kUnknownLegacy:
      LOG(FATAL) << "Unknown factor type should never be serialized";
    default:
      NOTIMPLEMENTED() << "Auth factor "
                       << static_cast<int>(factor.ref().type())
                       << " is not implemented in cryptohome yet.";
  }
}

void SerializeAuthInput(const AuthFactorRef& ref,
                        const AuthFactorInput& auth_input,
                        user_data_auth::AuthInput* out_proto) {
  DCHECK_EQ(ref.type(), auth_input.GetType());
  switch (auth_input.GetType()) {
    case AuthFactorType::kPassword:
      out_proto->mutable_password_input()->set_secret(
          auth_input.GetPasswordInput().hashed_password);
      break;
    case AuthFactorType::kPin:
      out_proto->mutable_pin_input()->set_secret(
          auth_input.GetPinInput().hashed_pin);
      break;
    case AuthFactorType::kRecovery: {
      auto* proto_input = out_proto->mutable_cryptohome_recovery_input();
      if (auth_input.UsableForAuthentication()) {
        const auto& recovery_auth = auth_input.GetRecoveryAuthenticationInput();
        proto_input->set_epoch_response(recovery_auth.epoch_data);
        proto_input->set_recovery_response(recovery_auth.recovery_data);
      } else {
        const auto& recovery_creation = auth_input.GetRecoveryCreationInput();
        proto_input->set_mediator_pub_key(recovery_creation.pub_key);
        proto_input->set_user_gaia_id(recovery_creation.user_gaia_id);
        proto_input->set_device_user_id(recovery_creation.device_user_id);
        proto_input->set_ensure_fresh_recovery_id(
            recovery_creation.ensure_fresh_recovery_id);
      }
    } break;
    case AuthFactorType::kKiosk:
      // Just create an input.
      out_proto->mutable_kiosk_input();
      break;
    case AuthFactorType::kSmartCard: {
      auto* proto_input = out_proto->mutable_smart_card_input();
      proto_input->set_key_delegate_dbus_service_name(
          auth_input.GetSmartCardInput().key_delegate_dbus_service_name);
      for (auto algorithm :
           auth_input.GetSmartCardInput().signature_algorithms) {
        proto_input->add_signature_algorithms(
            ChallengeSignatureAlgorithmToProtoEnum(algorithm));
      }
      break;
    }
    case AuthFactorType::kLegacyFingerprint:
      // Legacy Fingerprint does not use any information from the Ash side,
      // only the signal. Creating empty input for `oneof` to work.
      out_proto->mutable_legacy_fingerprint_input();
      break;
    case AuthFactorType::kFingerprint:
      // Fingerprint does not use any information from the Ash side,
      // only the signal. Creating empty input for `oneof` to work.
      out_proto->mutable_fingerprint_input();
      break;
    case AuthFactorType::kUnknownLegacy:
      LOG(FATAL) << "Unknown factor type should never be serialized";
    default:
      NOTIMPLEMENTED() << "Auth factor "
                       << static_cast<int>(auth_input.GetType())
                       << " is not implemented in cryptohome yet.";
      break;
  }
}

AuthFactor DeserializeAuthFactor(
    const user_data_auth::AuthFactorWithStatus& proto,
    AuthFactorType fallback_type) {
  CHECK(proto.has_auth_factor());
  auto factor_proto = proto.auth_factor();
  AuthFactorType type;
  if (factor_proto.type() == user_data_auth::AUTH_FACTOR_TYPE_UNSPECIFIED) {
    LOG(WARNING) << "Unspecified auth factor type found, treating it as a "
                 << static_cast<int>(fallback_type);
    type = fallback_type;
  } else {
    type = ConvertFactorTypeFromProto(factor_proto.type());
    // TODO(b/243808147): Remove this hack after fixing cryptohome to return
    // `AUTH_FACTOR_TYPE_UNSPECIFIED` for legacy kiosk keysets.
    if (fallback_type == cryptohome::AuthFactorType::kKiosk &&
        type != cryptohome::AuthFactorType::kKiosk) {
      LOG(WARNING) << "Fixup kiosk key type for " << factor_proto.label() << " "
                   << factor_proto.type();
      type = cryptohome::AuthFactorType::kKiosk;
    }
  }
  AuthFactorRef ref(type, KeyLabel{factor_proto.label()});
  ComponentVersion chrome_ver{kFallbackFactorVersion};
  ComponentVersion chromeos_ver{kFallbackFactorVersion};
  if (factor_proto.has_common_metadata()) {
    auto common_metadata_proto = factor_proto.common_metadata();
    if (!common_metadata_proto.chrome_version_last_updated().empty()) {
      chrome_ver =
          ComponentVersion(common_metadata_proto.chrome_version_last_updated());
    }
    if (!common_metadata_proto.chromeos_version_last_updated().empty()) {
      chromeos_ver = ComponentVersion(
          common_metadata_proto.chromeos_version_last_updated());
    }
  }
  AuthFactorCommonMetadata common_metadata{std::move(chrome_ver),
                                           std::move(chromeos_ver)};

  // Ignore is_active_for_login for now
  switch (type) {
    case AuthFactorType::kPassword: {
      auto password_metadata = ParsePasswordMetadata(factor_proto);
      return AuthFactor(std::move(ref), std::move(common_metadata),
                        std::move(password_metadata));
    }
    case AuthFactorType::kRecovery: {
      if (!factor_proto.has_cryptohome_recovery_metadata()) {
        return AuthFactor(std::move(ref), std::move(common_metadata));
      }
      CryptohomeRecoveryMetadata recovery_metadata;
      recovery_metadata.mediator_pub_key =
          factor_proto.cryptohome_recovery_metadata().mediator_pub_key();
      return AuthFactor(std::move(ref), std::move(common_metadata),
                        std::move(recovery_metadata));
    }
    case AuthFactorType::kKiosk:
      return AuthFactor(std::move(ref), std::move(common_metadata));
    case AuthFactorType::kPin: {
      DCHECK(factor_proto.has_pin_metadata());
      auto pin_metadata = ParsePinMetadata(factor_proto);
      PinStatus pin_status = proto.has_status_info()
                                 ? PasrePinFactorStatus(proto.status_info())
                                 : PinStatus();
      return AuthFactor(std::move(ref), std::move(common_metadata),
                        std::move(pin_metadata), std::move(pin_status));
    }
    case AuthFactorType::kSmartCard: {
      DCHECK(factor_proto.has_smart_card_metadata());
      SmartCardMetadata smart_card_metadata;
      smart_card_metadata.public_key_spki_der =
          factor_proto.smart_card_metadata().public_key_spki_der();
      return AuthFactor(std::move(ref), std::move(common_metadata),
                        std::move(smart_card_metadata));
    }
    case AuthFactorType::kLegacyFingerprint: {
      LOG(FATAL) << "Legacy fingerprint factor should never be returned"
                 << " by cryptohome.";
      __builtin_unreachable();
    }
    case AuthFactorType::kFingerprint: {
      DCHECK(factor_proto.has_fingerprint_metadata());
      FingerprintMetadata fingerprint_metadata;
      return AuthFactor(std::move(ref), std::move(common_metadata),
                        std::move(fingerprint_metadata));
    }
    case AuthFactorType::kUnknownLegacy:
      LOG(FATAL) << "Should already be handled above";
      __builtin_unreachable();
    default:
      NOTIMPLEMENTED() << "Auth factor " << static_cast<int>(type)
                       << " is not implemented in cryptohome yet.";
      return AuthFactor(std::move(ref), std::move(common_metadata));
  }
}

}  // namespace cryptohome