root/chrome/browser/metrics/variations/variations_seed_store.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. SignatureVerificationEnabled
  2. RecordVariationSeedEmptyHistogram
  3. TruncateToUTCDay
  4. GetSeedDateChangeState
  5. LoadSeed
  6. StoreSeedData
  7. UpdateSeedDateAndLogDayChange
  8. RegisterPrefs
  9. ClearPrefs
  10. VerifySeedSignature

// Copyright 2014 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 "chrome/browser/metrics/variations/variations_seed_store.h"

#include "base/base64.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
#include "base/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/common/pref_names.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "crypto/signature_verifier.h"

namespace chrome_variations {

namespace {

// Signature verification is disabled on mobile platforms for now, since it
// adds about ~15ms to the startup time on mobile (vs. a couple ms on desktop).
bool SignatureVerificationEnabled() {
#if defined(OS_IOS) || defined(OS_ANDROID)
  return false;
#else
  return true;
#endif
}

// This is the algorithm ID for ECDSA with SHA-256. Parameters are ABSENT.
// RFC 5758:
//   ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
//        us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 }
//   ...
//   When the ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-SHA384, or
//   ecdsa-with-SHA512 algorithm identifier appears in the algorithm field
//   as an AlgorithmIdentifier, the encoding MUST omit the parameters
//   field.  That is, the AlgorithmIdentifier SHALL be a SEQUENCE of one
//   component, the OID ecdsa-with-SHA224, ecdsa-with-SHA256, ecdsa-with-
//   SHA384, or ecdsa-with-SHA512.
// See also RFC 5480, Appendix A.
const uint8 kECDSAWithSHA256AlgorithmID[] = {
  0x30, 0x0a,
    0x06, 0x08,
      0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02,
};

// The ECDSA public key of the variations server for verifying variations seed
// signatures.
const uint8_t kPublicKey[] = {
  0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
  0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00,
  0x04, 0x51, 0x7c, 0x31, 0x4b, 0x50, 0x42, 0xdd, 0x59, 0xda, 0x0b, 0xfa, 0x43,
  0x44, 0x33, 0x7c, 0x5f, 0xa1, 0x0b, 0xd5, 0x82, 0xf6, 0xac, 0x04, 0x19, 0x72,
  0x6c, 0x40, 0xd4, 0x3e, 0x56, 0xe2, 0xa0, 0x80, 0xa0, 0x41, 0xb3, 0x23, 0x7b,
  0x71, 0xc9, 0x80, 0x87, 0xde, 0x35, 0x0d, 0x25, 0x71, 0x09, 0x7f, 0xb4, 0x15,
  0x2b, 0xff, 0x82, 0x4d, 0xd3, 0xfe, 0xc5, 0xef, 0x20, 0xc6, 0xa3, 0x10, 0xbf,
};

// Note: UMA histogram enum - don't re-order or remove entries.
enum VariationSeedEmptyState {
  VARIATIONS_SEED_NOT_EMPTY,
  VARIATIONS_SEED_EMPTY,
  VARIATIONS_SEED_CORRUPT,
  VARIATIONS_SEED_INVALID_SIGNATURE,
  VARIATIONS_SEED_EMPTY_ENUM_SIZE,
};

void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) {
  UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state,
                            VARIATIONS_SEED_EMPTY_ENUM_SIZE);
}

// Note: UMA histogram enum - don't re-order or remove entries.
enum VariationsSeedDateChangeState {
  SEED_DATE_NO_OLD_DATE,
  SEED_DATE_NEW_DATE_OLDER,
  SEED_DATE_SAME_DAY,
  SEED_DATE_NEW_DAY,
  SEED_DATE_ENUM_SIZE,
};

// Truncates a time to the start of the day in UTC. If given a time representing
// 2014-03-11 10:18:03.1 UTC, it will return a time representing
// 2014-03-11 00:00:00.0 UTC.
base::Time TruncateToUTCDay(const base::Time& time) {
  base::Time::Exploded exploded;
  time.UTCExplode(&exploded);
  exploded.hour = 0;
  exploded.minute = 0;
  exploded.second = 0;
  exploded.millisecond = 0;

  return base::Time::FromUTCExploded(exploded);
}

VariationsSeedDateChangeState GetSeedDateChangeState(
    const base::Time& server_seed_date,
    const base::Time& stored_seed_date) {
  if (server_seed_date < stored_seed_date)
    return SEED_DATE_NEW_DATE_OLDER;

  if (TruncateToUTCDay(server_seed_date) !=
      TruncateToUTCDay(stored_seed_date)) {
    // The server date is earlier than the stored date, and they are from
    // different UTC days, so |server_seed_date| is a valid new day.
    return SEED_DATE_NEW_DAY;
  }
  return SEED_DATE_SAME_DAY;
}

}  // namespace

VariationsSeedStore::VariationsSeedStore(PrefService* local_state)
    : local_state_(local_state) {
}

VariationsSeedStore::~VariationsSeedStore() {
}

bool VariationsSeedStore::LoadSeed(VariationsSeed* seed) {
  const std::string base64_seed_data =
      local_state_->GetString(prefs::kVariationsSeed);
  if (base64_seed_data.empty()) {
    RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY);
    return false;
  }

  // If the decode process fails, assume the pref value is corrupt and clear it.
  std::string seed_data;
  if (!base::Base64Decode(base64_seed_data, &seed_data) ||
      !seed->ParseFromString(seed_data)) {
    VLOG(1) << "Variations seed data in local pref is corrupt, clearing the "
            << "pref.";
    ClearPrefs();
    RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT);
    return false;
  }

  const std::string base64_seed_signature =
      local_state_->GetString(prefs::kVariationsSeedSignature);
  const VerifySignatureResult result =
      VerifySeedSignature(seed_data, base64_seed_signature);
  if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
    UMA_HISTOGRAM_ENUMERATION("Variations.LoadSeedSignature", result,
                              VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
    if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
      VLOG(1) << "Variations seed signature in local pref missing or invalid "
              << "with result: " << result << ". Clearing the pref.";
      ClearPrefs();
      RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_INVALID_SIGNATURE);
      return false;
    }
  }

  variations_serial_number_ = seed->serial_number();
  RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY);
  return true;
}

bool VariationsSeedStore::StoreSeedData(
    const std::string& seed_data,
    const std::string& base64_seed_signature,
    const base::Time& date_fetched) {
  if (seed_data.empty()) {
    VLOG(1) << "Variations seed data is empty, rejecting the seed.";
    return false;
  }

  // Only store the seed data if it parses correctly.
  VariationsSeed seed;
  if (!seed.ParseFromString(seed_data)) {
    VLOG(1) << "Variations seed data is not in valid proto format, "
            << "rejecting the seed.";
    return false;
  }

  const VerifySignatureResult result =
      VerifySeedSignature(seed_data, base64_seed_signature);
  if (result != VARIATIONS_SEED_SIGNATURE_ENUM_SIZE) {
    UMA_HISTOGRAM_ENUMERATION("Variations.StoreSeedSignature", result,
                              VARIATIONS_SEED_SIGNATURE_ENUM_SIZE);
    if (result != VARIATIONS_SEED_SIGNATURE_VALID) {
      VLOG(1) << "Variations seed signature missing or invalid with result: "
              << result << ". Rejecting the seed.";
      return false;
    }
  }

  std::string base64_seed_data;
  base::Base64Encode(seed_data, &base64_seed_data);

  // TODO(asvitkine): This pref is no longer being used. Remove it completely
  // in a couple of releases.
  local_state_->ClearPref(prefs::kVariationsSeedHash);

  local_state_->SetString(prefs::kVariationsSeed, base64_seed_data);
  UpdateSeedDateAndLogDayChange(date_fetched);
  local_state_->SetString(prefs::kVariationsSeedSignature,
                          base64_seed_signature);
  variations_serial_number_ = seed.serial_number();

  return true;
}

void VariationsSeedStore::UpdateSeedDateAndLogDayChange(
    const base::Time& server_date_fetched) {
  VariationsSeedDateChangeState date_change = SEED_DATE_NO_OLD_DATE;

  if (local_state_->HasPrefPath(prefs::kVariationsSeedDate)) {
    const int64 stored_date_value =
        local_state_->GetInt64(prefs::kVariationsSeedDate);
    const base::Time stored_date =
        base::Time::FromInternalValue(stored_date_value);

    date_change = GetSeedDateChangeState(server_date_fetched, stored_date);
  }

  UMA_HISTOGRAM_ENUMERATION("Variations.SeedDateChange", date_change,
                            SEED_DATE_ENUM_SIZE);

  local_state_->SetInt64(prefs::kVariationsSeedDate,
                         server_date_fetched.ToInternalValue());
}

// static
void VariationsSeedStore::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterStringPref(prefs::kVariationsSeed, std::string());
  registry->RegisterStringPref(prefs::kVariationsSeedHash, std::string());
  registry->RegisterInt64Pref(prefs::kVariationsSeedDate,
                              base::Time().ToInternalValue());
  registry->RegisterStringPref(prefs::kVariationsSeedSignature, std::string());
}

void VariationsSeedStore::ClearPrefs() {
  local_state_->ClearPref(prefs::kVariationsSeed);
  local_state_->ClearPref(prefs::kVariationsSeedDate);
  local_state_->ClearPref(prefs::kVariationsSeedHash);
  local_state_->ClearPref(prefs::kVariationsSeedSignature);
}

VariationsSeedStore::VerifySignatureResult
VariationsSeedStore::VerifySeedSignature(
    const std::string& seed_bytes,
    const std::string& base64_seed_signature) {
  if (!SignatureVerificationEnabled())
    return VARIATIONS_SEED_SIGNATURE_ENUM_SIZE;

  if (base64_seed_signature.empty())
    return VARIATIONS_SEED_SIGNATURE_MISSING;

  std::string signature;
  if (!base::Base64Decode(base64_seed_signature, &signature))
    return VARIATIONS_SEED_SIGNATURE_DECODE_FAILED;

  crypto::SignatureVerifier verifier;
  if (!verifier.VerifyInit(
          kECDSAWithSHA256AlgorithmID, sizeof(kECDSAWithSHA256AlgorithmID),
          reinterpret_cast<const uint8*>(signature.data()), signature.size(),
          kPublicKey, arraysize(kPublicKey))) {
    return VARIATIONS_SEED_SIGNATURE_INVALID_SIGNATURE;
  }

  verifier.VerifyUpdate(reinterpret_cast<const uint8*>(seed_bytes.data()),
                        seed_bytes.size());
  if (verifier.VerifyFinal())
    return VARIATIONS_SEED_SIGNATURE_VALID;
  return VARIATIONS_SEED_SIGNATURE_INVALID_SEED;
}

}  // namespace chrome_variations

/* [<][>][^][v][top][bottom][index][help] */