blob: 3e6ebb10b346491bb25f2ac7c8187c5797efb3b9 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "featured/hmac.h"
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <absl/cleanup/cleanup.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/logging.h>
namespace featured {
// Ints because RAND_priv_bytes takes an int.
constexpr int kKeyBits = 256; // 32 bytes
constexpr int kKeyBytes = kKeyBits / 8;
namespace {
// Log all errors.
void LogOpenSSLError(const std::string& context) {
LOG(ERROR) << "Failure: " << context << ". OpenSSL context, if any, follows.";
ERR_print_errors_cb(
[](const char* str, size_t len, void* context) -> int {
std::string s(str, len);
LOG(ERROR) << reinterpret_cast<const char*>(context) << ": " << s;
return 1; // report success.
},
const_cast<void*>(reinterpret_cast<const void*>(context.data())));
}
} // namespace
HMAC::HMAC(HMAC::HashAlgorithm alg) : hash_alg_(alg) {}
HMAC::~HMAC() {
ZeroData();
}
// ZeroData fills memory with null bytes to clear confidential data (e.g. keys)
void HMAC::ZeroData() {
if (!key_.empty()) {
OPENSSL_cleanse(key_.data(), key_.size());
}
HMAC_CTX_free(ctx_);
ctx_ = nullptr;
}
bool HMAC::Init() {
return Init("");
}
bool HMAC::Init(base::StringPiece key) {
// Perhaps we'll support more later, but for now SHA256 suffices.
DCHECK_EQ(hash_alg_, SHA256);
// Make sure we're starting from a clean slate, and wipe out any pre-existing
// key data.
ZeroData();
key_.clear();
ctx_ = HMAC_CTX_new();
if (ctx_ == nullptr) {
LogOpenSSLError("Init: HMAC_CTX_new");
return false;
}
if (!key.empty()) {
key_.assign(key.begin(), key.end());
} else {
// Allocate sufficient space.
key_.resize(kKeyBytes);
// Generate the key.
int rv = RAND_priv_bytes(reinterpret_cast<unsigned char*>(key_.data()),
kKeyBytes);
if (rv != 1) {
LogOpenSSLError("Init - RandPrivBytes");
ZeroData();
// ctx_ will be nullptr so the class will be unusable.
return false;
}
}
int rv = HMAC_Init_ex(ctx_, key_.data(), key_.size(), EVP_sha256(), nullptr);
if (rv != 1) {
LogOpenSSLError("Init - HMAC_Init_ex");
ZeroData();
return false;
}
return true;
}
std::optional<std::string> HMAC::Sign(base::StringPiece data) const {
DCHECK_NE(ctx_, nullptr) << "Class not initialized";
if (ctx_ == nullptr) {
// Safeguard in case code using empty key sneaks through to prod -- better
// to fail than allow signatures using no key.
// We don't CHECK in case a subtle bug occurs causing an untested path to be
// uncovered in prod.
return std::nullopt;
}
// Reuse same key, but otherwise use a fresh ctx.
// (Otherwise, a second Sign would reuse data from the first Sign, or a Verify
// after a Sign would reuse data from the first Sign.)
HMAC_CTX* ctx_for_sign = HMAC_CTX_new();
if (ctx_for_sign == nullptr) {
LogOpenSSLError("Sign - HMAC_CTX_new");
return std::nullopt;
}
// Ensure ctx_for_sign is freed.
absl::Cleanup free_ctx = [ctx_for_sign]() { HMAC_CTX_free(ctx_for_sign); };
int rv = HMAC_CTX_copy(ctx_for_sign, ctx_);
if (rv != 1) {
LogOpenSSLError("Sign - HMAC_CTX_copy");
return std::nullopt;
}
rv = HMAC_Update(ctx_for_sign,
reinterpret_cast<const unsigned char*>(data.data()),
data.size());
if (rv != 1) {
LogOpenSSLError("Sign - HMAC_Update");
return std::nullopt;
}
// Allocate sufficient space.
uint8_t digest_data[EVP_MAX_MD_SIZE] = {0};
unsigned int output_size = 0;
rv = HMAC_Final(ctx_for_sign, digest_data, &output_size);
if (rv != 1) {
LogOpenSSLError("Sign: HMAC_final");
return std::nullopt;
}
return std::string(reinterpret_cast<char*>(digest_data), output_size);
}
bool HMAC::Verify(base::StringPiece data, base::StringPiece hmac) const {
std::optional<std::string> actual = Sign(data);
if (!actual.has_value()) {
// Fail closed.
return false;
}
if (actual->size() != hmac.size()) {
return false;
}
return CRYPTO_memcmp(actual->c_str(), hmac.data(), actual->size()) == 0;
}
} // namespace featured