// Copyright (c) 2012 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/nacl/nacl_validation_query.h" #include "base/logging.h" #include "crypto/nss_util.h" #include "chrome/nacl/nacl_validation_db.h" #include "native_client/src/trusted/validator/validation_cache.h" NaClValidationQueryContext::NaClValidationQueryContext( NaClValidationDB* db, const std::string& profile_key, const std::string& nacl_version) : db_(db), profile_key_(profile_key), nacl_version_(nacl_version) { // Sanity checks. CHECK(profile_key.length() >= 8); CHECK(nacl_version.length() >= 4); } NaClValidationQuery* NaClValidationQueryContext::CreateQuery() { NaClValidationQuery* query = new NaClValidationQuery(db_, profile_key_); // Changing the version effectively invalidates existing hashes. query->AddData(nacl_version_); return query; } NaClValidationQuery::NaClValidationQuery(NaClValidationDB* db, const std::string& profile_key) : state_(READY), hasher_(crypto::HMAC::SHA256), db_(db), buffer_length_(0) { // Without this line on Linux, HMAC::Init will instantiate a singleton that // in turn attempts to open a file. Disabling this behavior avoids a ~70 ms // stall the first time HMAC is used. // This function is also called in nacl_helper_linux.cc, but nacl_helper may // not be used in all cases. // TODO(ncbray) remove when nacl_helper becomes the only code path. // http://code.google.com/p/chromium/issues/detail?id=118263 #if defined(OS_LINUX) crypto::ForceNSSNoDBInit(); #endif CHECK(hasher_.Init(profile_key)); } void NaClValidationQuery::AddData(const char* data, size_t length) { CHECK(state_ == READY); CHECK(buffer_length_ >= 0); CHECK(buffer_length_ <= (int) sizeof(buffer_)); // Chrome's HMAC class doesn't support incremental signing. Work around // this by using a (small) temporary buffer to accumulate data. // Check if there is space in the buffer. if (buffer_length_ + kDigestLength > (int) sizeof(buffer_)) { // Hash the buffer to make space. CompressBuffer(); } // Hash the input data into the buffer. Assumes that sizeof(buffer_) >= // kDigestLength * 2 (the buffer can store at least two digests.) CHECK(hasher_.Sign(base::StringPiece(data, length), reinterpret_cast(buffer_ + buffer_length_), kDigestLength)); buffer_length_ += kDigestLength; } void NaClValidationQuery::AddData(const unsigned char* data, size_t length) { AddData(reinterpret_cast(data), length); } void NaClValidationQuery::AddData(const base::StringPiece& data) { AddData(data.data(), data.length()); } int NaClValidationQuery::QueryKnownToValidate() { CHECK(state_ == READY); // It is suspicious if we have less than a digest's worth of data. CHECK(buffer_length_ >= kDigestLength); CHECK(buffer_length_ <= (int) sizeof(buffer_)); state_ = GET_CALLED; // Ensure the buffer contains only one digest worth of data. CompressBuffer(); return db_->QueryKnownToValidate(std::string(buffer_, buffer_length_)); } void NaClValidationQuery::SetKnownToValidate() { CHECK(state_ == GET_CALLED); CHECK(buffer_length_ == kDigestLength); state_ = SET_CALLED; db_->SetKnownToValidate(std::string(buffer_, buffer_length_)); } // Reduce the size of the data in the buffer by hashing it and writing it back // to the buffer. void NaClValidationQuery::CompressBuffer() { // Calculate the digest into a temp buffer. It is likely safe to calculate it // directly back into the buffer, but this is an "accidental" semantic we're // avoiding depending on. unsigned char temp[kDigestLength]; CHECK(hasher_.Sign(base::StringPiece(buffer_, buffer_length_), temp, kDigestLength)); memcpy(buffer_, temp, kDigestLength); buffer_length_ = kDigestLength; } // OO wrappers static void* CreateQuery(void* handle) { return static_cast(handle)->CreateQuery(); } static void AddData(void* query, const uint8* data, size_t length) { static_cast(query)->AddData(data, length); } static int QueryKnownToValidate(void* query) { return static_cast(query)->QueryKnownToValidate(); } static void SetKnownToValidate(void* query) { static_cast(query)->SetKnownToValidate(); } static void DestroyQuery(void* query) { delete static_cast(query); } struct NaClValidationCache* CreateValidationCache( NaClValidationDB* db, const std::string& profile_key, const std::string& nacl_version) { NaClValidationCache* cache = static_cast(malloc(sizeof(NaClValidationCache))); // Make sure any fields introduced in a cross-repo change are zeroed. memset(cache, 0, sizeof(*cache)); cache->handle = new NaClValidationQueryContext(db, profile_key, nacl_version); cache->CreateQuery = CreateQuery; cache->AddData = AddData; cache->QueryKnownToValidate = QueryKnownToValidate; cache->SetKnownToValidate = SetKnownToValidate; cache->DestroyQuery = DestroyQuery; return cache; }