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

#include "device/fido/mac/credential_metadata.h"

#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "components/cbor/cbor_reader.h"
#include "components/cbor/cbor_values.h"
#include "components/cbor/cbor_writer.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/hkdf.h"
#include "third_party/boringssl/src/include/openssl/rand.h"

namespace device {
namespace fido {
namespace mac {

using cbor::CBORWriter;
using cbor::CBORReader;
using cbor::CBORValue;

// static
std::string CredentialMetadata::GenerateRandomSecret() {
  static constexpr size_t kSecretSize = 32u;
  std::string secret;
  RAND_bytes(
      reinterpret_cast<uint8_t*>(base::WriteInto(&secret, kSecretSize + 1)),
      kSecretSize);
  return secret;
}

CredentialMetadata::CredentialMetadata(const std::string& secret)
    : secret_(secret) {}
CredentialMetadata::~CredentialMetadata() = default;

// static
CredentialMetadata::UserEntity
CredentialMetadata::UserEntity::FromPublicKeyCredentialUserEntity(
    const PublicKeyCredentialUserEntity& user) {
  return CredentialMetadata::UserEntity(user.user_id(),
                                        user.user_name().value_or(""),
                                        user.user_display_name().value_or(""));
}

PublicKeyCredentialUserEntity
CredentialMetadata::UserEntity::ToPublicKeyCredentialUserEntity() {
  auto user_entity = PublicKeyCredentialUserEntity(id);
  if (!name.empty()) {
    user_entity.SetUserName(name);
  }
  if (!display_name.empty()) {
    user_entity.SetDisplayName(display_name);
  }
  return user_entity;
}

CredentialMetadata::UserEntity::UserEntity(std::vector<uint8_t> id_,
                                           std::string name_,
                                           std::string display_name_)
    : id(std::move(id_)),
      name(std::move(name_)),
      display_name(std::move(display_name_)) {}
CredentialMetadata::UserEntity::UserEntity(
    const CredentialMetadata::UserEntity&) = default;
CredentialMetadata::UserEntity::UserEntity(CredentialMetadata::UserEntity&&) =
    default;
CredentialMetadata::UserEntity& CredentialMetadata::UserEntity::operator=(
    CredentialMetadata::UserEntity&&) = default;
CredentialMetadata::UserEntity::~UserEntity() = default;

static constexpr size_t kNonceLength = 12;

// static
base::Optional<std::vector<uint8_t>> CredentialMetadata::SealCredentialId(
    const std::string& secret,
    const std::string& rp_id,
    const UserEntity& user) {
  CredentialMetadata cryptor(secret);

  // The first 13 bytes are the version and nonce.
  std::vector<uint8_t> result(1 + kNonceLength);
  result[0] = kVersion;
  // Pick a random nonce. N.B. the nonce is similar to an IV. It needs to be
  // distinct (but not necessarily random). Nonce reuse breaks confidentiality
  // (in particular, it leaks the XOR of the plaintexts encrypted under the
  // same nonce and key).
  base::span<uint8_t> nonce(result.data() + 1, kNonceLength);
  RAND_bytes(nonce.data(), nonce.size());  // RAND_bytes always returns 1.

  // The remaining bytes are the CBOR-encoded UserEntity, encrypted with
  // AES-256-GCM and authenticated with the version and RP ID.
  CBORValue::ArrayValue cbor_user;
  cbor_user.emplace_back(CBORValue(user.id));
  cbor_user.emplace_back(CBORValue(user.name, CBORValue::Type::BYTE_STRING));
  cbor_user.emplace_back(
      CBORValue(user.display_name, CBORValue::Type::BYTE_STRING));
  base::Optional<std::vector<uint8_t>> pt =
      CBORWriter::Write(CBORValue(std::move(cbor_user)));
  if (!pt) {
    return base::nullopt;
  }
  base::Optional<std::string> ciphertext = cryptor.Seal(
      CredentialMetadata::Algorithm::kAes256Gcm, nonce, *pt, MakeAad(rp_id));
  if (!ciphertext) {
    return base::nullopt;
  }
  base::span<const char> cts(reinterpret_cast<const char*>(ciphertext->data()),
                             ciphertext->size());
  result.insert(result.end(), cts.begin(), cts.end());
  return result;
}

// static
base::Optional<CredentialMetadata::UserEntity>
CredentialMetadata::UnsealCredentialId(
    const std::string& secret,
    const std::string& rp_id,
    base::span<const uint8_t> credential_id) {
  CredentialMetadata cryptor(secret);

  // Recover the nonce and check for the correct version byte. Then try to
  // decrypt the remaining bytes.
  if (credential_id.size() <= 1 + kNonceLength ||
      credential_id[0] != kVersion) {
    return base::nullopt;
  }

  base::Optional<std::string> plaintext =
      cryptor.Unseal(CredentialMetadata::Algorithm::kAes256Gcm,
                     credential_id.subspan(1, kNonceLength),
                     credential_id.subspan(1 + kNonceLength), MakeAad(rp_id));
  if (!plaintext) {
    return base::nullopt;
  }

  // The recovered plaintext should decode into the UserEntity struct.
  base::Optional<CBORValue> maybe_array = CBORReader::Read(base::make_span(
      reinterpret_cast<const uint8_t*>(plaintext->data()), plaintext->size()));
  if (!maybe_array || !maybe_array->is_array()) {
    return base::nullopt;
  }
  const CBORValue::ArrayValue& array = maybe_array->GetArray();
  if (array.size() != 3 || !array[0].is_bytestring() ||
      !array[1].is_bytestring() || !array[2].is_bytestring()) {
    return base::nullopt;
  }
  return UserEntity(array[0].GetBytestring(),
                    array[1].GetBytestringAsString().as_string(),
                    array[2].GetBytestringAsString().as_string());
}

// static
base::Optional<std::string> CredentialMetadata::EncodeRpIdAndUserId(
    const std::string& secret,
    const std::string& rp_id,
    base::span<const uint8_t> user_id) {
  // Encoding RP ID along with the user ID hides whether the same user ID was
  // reused on different RPs.
  const auto* user_id_data = reinterpret_cast<const char*>(user_id.data());
  return CredentialMetadata(secret).HmacForStorage(
      rp_id + "/" + std::string(user_id_data, user_id_data + user_id.size()));
}

// static
base::Optional<std::string> CredentialMetadata::EncodeRpId(
    const std::string& secret,
    const std::string& rp_id) {
  // Encrypt with a fixed nonce to make the result deterministic while still
  // allowing the RP ID to be recovered from the ciphertext later.
  static constexpr std::array<uint8_t, kNonceLength> fixed_zero_nonce = {};
  base::span<const uint8_t> pt(reinterpret_cast<const uint8_t*>(rp_id.data()),
                               rp_id.size());
  std::string empty_ad;
  // Using AES-GCM with a fixed nonce would break confidentiality, so this uses
  // AES-GCM-SIV instead.
  base::Optional<std::string> ct = CredentialMetadata(secret).Seal(
      CredentialMetadata::Algorithm::kAes256GcmSiv, fixed_zero_nonce, pt,
      empty_ad);
  if (!ct) {
    return base::nullopt;
  }
  // The keychain field that stores the encrypted RP ID only accepts NSString
  // (not NSData), so we HexEncode to ensure the result is UTF-8-decodable.
  return base::HexEncode(ct->data(), ct->size());
}

// static
base::Optional<std::string> CredentialMetadata::DecodeRpId(
    const std::string& secret,
    const std::string& ciphertext) {
  std::vector<uint8_t> ct;
  if (!base::HexStringToBytes(ciphertext, &ct)) {
    return base::nullopt;
  }
  static constexpr std::array<uint8_t, kNonceLength> fixed_zero_nonce = {};
  std::string empty_ad;
  return CredentialMetadata(secret).Unseal(
      CredentialMetadata::Algorithm::kAes256GcmSiv, fixed_zero_nonce, ct,
      empty_ad);
}

// static
std::string CredentialMetadata::MakeAad(const std::string& rp_id) {
  return std::string(1, kVersion) + rp_id;
}

// static
std::string CredentialMetadata::DeriveKey(base::StringPiece secret,
                                          Algorithm alg) {
  static constexpr size_t kKeyLength = 32u;
  std::string key;
  const uint8_t info = static_cast<uint8_t>(alg);
  const bool hkdf_init = ::HKDF(
      reinterpret_cast<uint8_t*>(base::WriteInto(&key, kKeyLength + 1)),
      kKeyLength, EVP_sha256(), reinterpret_cast<const uint8_t*>(secret.data()),
      secret.size(), nullptr /* salt */, 0, &info, 1);
  DCHECK(hkdf_init);
  return key;
}

// static
base::Optional<crypto::Aead::AeadAlgorithm> CredentialMetadata::ToAeadAlgorithm(
    Algorithm alg) {
  switch (alg) {
    case CredentialMetadata::Algorithm::kAes256Gcm:
      return crypto::Aead::AES_256_GCM;
    case CredentialMetadata::Algorithm::kAes256GcmSiv:
      return crypto::Aead::AES_256_GCM_SIV;
    case CredentialMetadata::Algorithm::kHmacSha256:
      NOTREACHED() << "invalid AEAD";
      return base::nullopt;
  }
}

base::Optional<std::string> CredentialMetadata::Seal(
    CredentialMetadata::Algorithm algorithm,
    base::span<const uint8_t> nonce,
    base::span<const uint8_t> plaintext,
    base::StringPiece authenticated_data) const {
  auto opt_aead_algorithm = ToAeadAlgorithm(algorithm);
  if (!opt_aead_algorithm)
    return base::nullopt;

  const std::string key = DeriveKey(secret_, algorithm);
  crypto::Aead aead(*opt_aead_algorithm);
  aead.Init(&key);
  std::string ciphertext;
  if (!aead.Seal(
          base::StringPiece(reinterpret_cast<const char*>(plaintext.data()),
                            plaintext.size()),
          base::StringPiece(reinterpret_cast<const char*>(nonce.data()),
                            nonce.size()),
          authenticated_data, &ciphertext)) {
    return base::nullopt;
  }
  return ciphertext;
}

base::Optional<std::string> CredentialMetadata::Unseal(
    CredentialMetadata::Algorithm algorithm,
    base::span<const uint8_t> nonce,
    base::span<const uint8_t> ciphertext,
    base::StringPiece authenticated_data) const {
  auto opt_aead_algorithm = ToAeadAlgorithm(algorithm);
  if (!opt_aead_algorithm)
    return base::nullopt;

  const std::string key = DeriveKey(secret_, algorithm);
  crypto::Aead aead(*opt_aead_algorithm);
  aead.Init(&key);
  std::string plaintext;
  if (!aead.Open(
          base::StringPiece(reinterpret_cast<const char*>(ciphertext.data()),
                            ciphertext.size()),
          base::StringPiece(reinterpret_cast<const char*>(nonce.data()),
                            nonce.size()),
          authenticated_data, &plaintext)) {
    return base::nullopt;
  }
  return plaintext;
}

base::Optional<std::string> CredentialMetadata::HmacForStorage(
    base::StringPiece data) const {
  crypto::HMAC hmac(crypto::HMAC::SHA256);
  const std::string key = DeriveKey(secret_, Algorithm::kHmacSha256);
  std::vector<uint8_t> digest(hmac.DigestLength());
  if (!hmac.Init(key) || !hmac.Sign(data, digest.data(), hmac.DigestLength())) {
    return base::nullopt;
  }
  // The keychain fields that store RP ID and User ID seem to only accept
  // NSString (not NSData), so we HexEncode to ensure the result to be
  // UTF-8-decodable.
  return base::HexEncode(digest.data(), digest.size());
}

}  // namespace mac
}  // namespace fido
}  // namespace device
