blob: afb8dfd6757d9a8bd8deba5a27e253f5ef2be315 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "algorithms/forculus/forculus_analyzer.h"
#include <glog/logging.h>
#include "algorithms/forculus/forculus_utils.h"
#include "util/crypto_util/base64.h"
#include "util/log_based_metrics.h"
namespace cobalt {
namespace forculus {
// Stackdriver metric constants
namespace {
const char kAddObservationFailure[] =
"forculus-analyzer-add-observation-failure";
} // namespace
namespace {
// Produces a string used in an error message to describe the observation.
std::string ErrorString(const ForculusObservation& obs) {
std::string ciphertext;
std::string point_x;
std::string point_y;
crypto::Base64Encode(obs.ciphertext(), &ciphertext);
crypto::Base64Encode(obs.point_x(), &point_x);
crypto::Base64Encode(obs.point_y(), &point_y);
return "ciphertext=" + ciphertext + " x=" + point_x + " y=" + point_y;
}
} // namespace
ForculusAnalyzer::ForculusAnalyzer(const cobalt::ForculusConfig& config)
: config_(config) {}
bool ForculusAnalyzer::AddObservation(uint32_t day_index,
const ForculusObservation& obs) {
// Compute the epoch_index from the day_index.
uint32_t epoch_index =
EpochIndexFromDayIndex(day_index, config_.epoch_type());
// Look in decryption_map for our (day_index, obs) pair.
DecrypterGroupKey group_key(epoch_index, obs.ciphertext());
auto decryption_map_iter = decryption_map_.find(group_key);
if (decryption_map_iter == decryption_map_.end()) {
// There was no entry for this group_key in decryption_map. Create a
// new ForculusDecrypter and a new entry.
std::unique_ptr<ForculusDecrypter> decrypter(
new ForculusDecrypter(config_.threshold(), obs.ciphertext()));
decrypter->AddObservation(obs);
decryption_map_.emplace(group_key, DecrypterResult(std::move(decrypter)));
} else {
// There is already an entry in encryption map.
DecrypterResult& decrypter_result = decryption_map_iter->second;
if (decrypter_result.result_info) {
// The ciphertext has already been decrypted. Just increment the count.
decrypter_result.result_info->total_count++;
} else {
// The ciphertext has not yet been decrypted. Add this additional
// observation and let's see if that pushes us over the threshold.
if (!decrypter_result.decrypter) {
// We have previously deleted the decrypter object because it was
// in an inconsistent state.
LOG_STACKDRIVER_COUNT_METRIC(ERROR, kAddObservationFailure)
<< "Skipping decryption because of a previous error: "
<< "day_index=" << day_index << " " << ErrorString(obs);
observation_errors_++;
return false;
}
if (decrypter_result.decrypter->AddObservation(obs) !=
ForculusDecrypter::kOK) {
// Delete the Decrypter object. It is in an inconsistent state.
LOG_STACKDRIVER_COUNT_METRIC(ERROR, kAddObservationFailure)
<< "Found inconsistent observation. Deleting Decrypter: "
<< ErrorString(obs);
decrypter_result.decrypter.reset();
observation_errors_++;
return false;
}
if (decrypter_result.decrypter->size() >= config_.threshold()) {
// We are now able to decrypt the ciphertext.
std::string recovered_text;
if (decrypter_result.decrypter->Decrypt(&recovered_text) !=
ForculusDecrypter::kOK) {
// Delete the Decrypter object. It is in an inconsistent state.
LOG_STACKDRIVER_COUNT_METRIC(ERROR, kAddObservationFailure)
<< "Decryption failed. Deleting Decrypter: " << ErrorString(obs);
decrypter_result.decrypter.reset();
observation_errors_++;
return false;
}
uint32_t num_seen = decrypter_result.decrypter->num_seen();
// Delete the Decrypter object. It has done its job and we don't need
// it anymore.
VLOG(4) << "Decryption succeeded: '" << recovered_text
<< "' Deleting Decrypter: day_index=" << day_index << " "
<< ErrorString(obs);
decrypter_result.decrypter.reset();
auto results_iter = results_.find(recovered_text);
if (results_iter == results_.end()) {
// This is the first time this recovered_text has been seen. Make
// a new ResultInfo.
std::unique_ptr<ResultInfo> result_info(new ResultInfo(num_seen));
// Keep a non-owned pointer to result_info in the decrypter_map
// so we can find it quickly the next time we get another observation
// with the same group_key.
decrypter_result.result_info = result_info.get();
// Keep the owned pointer in results_.
results_.emplace(std::move(recovered_text), std::move(result_info));
} else {
// This recovered text has been seen before. This happens when
// we are analyzing more than one Forculus epoch and this same
// recovered text was seen in a different epoch.
auto& result_info = results_iter->second;
result_info->num_epochs++;
result_info->total_count += num_seen;
// Keep a non-owned pointer to result_info in the decrypter_map
// so we can find it quickly the next time we get another observation
// with the same group_key.
decrypter_result.result_info = result_info.get();
}
}
}
}
num_observations_++;
return true;
}
size_t ForculusAnalyzer::KeyHasher::operator()(
const DecrypterGroupKey& key) const {
// The probability of having the same ciphertext with two different
// epoch_indexes is negligably small since the epoch_index was one of
// the ingredients that went into the master key during encryption. For
// this reason we use the hash of the ciphertext alone as the hash of the
// pair.
return std::hash<std::string>()(key.ciphertext);
}
} // namespace forculus
} // namespace cobalt