blob: daf974633e3b47084d481ffb93f74e4df3194016 [file] [log] [blame]
// Copyright 2021 The Chromium OS 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 "cryptohome/cryptorecovery/fake_recovery_mediator_crypto.h"
#include <algorithm>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/optional.h>
#include <base/stl_util.h>
#include <brillo/secure_blob.h>
#include "cryptohome/crypto/aes.h"
#include "cryptohome/crypto/big_num_util.h"
#include "cryptohome/crypto/ecdh_hkdf.h"
#include "cryptohome/crypto/elliptic_curve.h"
#include "cryptohome/crypto/error_util.h"
#include "cryptohome/crypto/secure_blob_util.h"
#include "cryptohome/cryptohome_common.h"
#include "cryptohome/cryptorecovery/recovery_crypto.h"
#include "cryptohome/cryptorecovery/recovery_crypto_hsm_cbor_serialization.h"
#include "cryptohome/cryptorecovery/recovery_crypto_util.h"
namespace cryptohome {
namespace cryptorecovery {
namespace {
const char kFakeHsmMetaData[] = "fake-hsm-metadata";
brillo::SecureBlob GetMediatorShareHkdfInfo() {
return brillo::SecureBlob(RecoveryCrypto::kMediatorShareHkdfInfoValue);
}
brillo::SecureBlob GetRequestPayloadPlainTextHkdfInfo() {
return brillo::SecureBlob(
RecoveryCrypto::kRequestPayloadPlainTextHkdfInfoValue);
}
brillo::SecureBlob GetResponsePayloadPlainTextHkdfInfo() {
return brillo::SecureBlob(
RecoveryCrypto::kResponsePayloadPlainTextHkdfInfoValue);
}
} // namespace
// Hardcoded fake mediator and epoch public and private keys. Do not use them in
// production! Keys were generated at random using
// EllipticCurve::GenerateKeysAsSecureBlobs method and converted to hex.
static const char kFakeMediatorPublicKeyHex[] =
"041C66FD08151D1C34EA5003F7C24557D2E4802535AA4F65EDBE3CD495CFE060387D00D5D2"
"5D859B26C5134F1AD00F2230EAB72A47F46DF23407CF68FB18C509DE";
static const char kFakeMediatorPrivateKeyHex[] =
"B7A01DA624ECF448D9F7E1B07236EA2930A17C9A31AD60E43E01A8FEA934AB1C";
static const char kFakeEpochPrivateKeyHex[] =
"2DC064DBE7473CE2E617C689E3D1D71568E1B09EA6CEC5CB4463A66C06F1B535";
static const char kFakeEpochPublicKeyHex[] =
"045D8393CDEF671228CB0D8454BBB6F2AAA18E05834BB6DBBD05721FC81ED3BED33D08A8EF"
"D44F6786CAE7ADEB8E26A355CD9714F59C78F063A3CA3A7D74877A8A";
std::unique_ptr<FakeRecoveryMediatorCrypto>
FakeRecoveryMediatorCrypto::Create() {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return nullptr;
}
base::Optional<EllipticCurve> ec =
EllipticCurve::Create(RecoveryCrypto::kCurve, context.get());
if (!ec) {
LOG(ERROR) << "Failed to create EllipticCurve";
return nullptr;
}
return base::WrapUnique(new FakeRecoveryMediatorCrypto(std::move(*ec)));
}
FakeRecoveryMediatorCrypto::FakeRecoveryMediatorCrypto(EllipticCurve ec)
: ec_(std::move(ec)) {}
// static
bool FakeRecoveryMediatorCrypto::GetFakeMediatorPublicKey(
brillo::SecureBlob* mediator_pub_key) {
if (!brillo::SecureBlob::HexStringToSecureBlob(kFakeMediatorPublicKeyHex,
mediator_pub_key)) {
LOG(ERROR) << "Failed to convert hex to SecureBlob";
return false;
}
return true;
}
// static
bool FakeRecoveryMediatorCrypto::GetFakeMediatorPrivateKey(
brillo::SecureBlob* mediator_priv_key) {
if (!brillo::SecureBlob::HexStringToSecureBlob(kFakeMediatorPrivateKeyHex,
mediator_priv_key)) {
LOG(ERROR) << "Failed to convert hex to SecureBlob";
return false;
}
return true;
}
// static
bool FakeRecoveryMediatorCrypto::GetFakeEpochPublicKey(
brillo::SecureBlob* epoch_pub_key) {
if (!brillo::SecureBlob::HexStringToSecureBlob(kFakeEpochPublicKeyHex,
epoch_pub_key)) {
LOG(ERROR) << "Failed to convert hex to SecureBlob";
return false;
}
return true;
}
// static
bool FakeRecoveryMediatorCrypto::GetFakeEpochPrivateKey(
brillo::SecureBlob* epoch_priv_key) {
if (!brillo::SecureBlob::HexStringToSecureBlob(kFakeEpochPrivateKeyHex,
epoch_priv_key)) {
LOG(ERROR) << "Failed to convert hex to SecureBlob";
return false;
}
return true;
}
bool FakeRecoveryMediatorCrypto::DecryptMediatorShare(
const brillo::SecureBlob& mediator_priv_key,
const RecoveryCrypto::EncryptedMediatorShare& encrypted_mediator_share,
brillo::SecureBlob* mediator_share) const {
brillo::SecureBlob aes_gcm_key;
if (!GenerateEcdhHkdfRecipientKey(
ec_, mediator_priv_key, encrypted_mediator_share.ephemeral_pub_key,
GetMediatorShareHkdfInfo(),
/*hkdf_salt=*/brillo::SecureBlob(), RecoveryCrypto::kHkdfHash,
kAesGcm256KeySize, &aes_gcm_key)) {
LOG(ERROR) << "Failed to generate ECDH+HKDF recipient key for mediator "
"share decryption";
return false;
}
if (!AesGcmDecrypt(encrypted_mediator_share.encrypted_data,
/*ad=*/base::nullopt, encrypted_mediator_share.tag,
aes_gcm_key, encrypted_mediator_share.iv,
mediator_share)) {
LOG(ERROR) << "Failed to perform AES-GCM decryption";
return false;
}
return true;
}
bool FakeRecoveryMediatorCrypto::DecryptHsmPayloadPlainText(
const brillo::SecureBlob& mediator_priv_key,
const HsmPayload& hsm_payload,
brillo::SecureBlob* plain_text) const {
brillo::SecureBlob publisher_pub_key;
if (!GetBytestringValueFromCborMapByKeyForTesting(hsm_payload.associated_data,
kPublisherPublicKey,
&publisher_pub_key)) {
LOG(ERROR) << "Unable to deserialize publisher_pub_key from hsm_payload";
return false;
}
brillo::SecureBlob aes_gcm_key;
if (!GenerateEcdhHkdfRecipientKey(
ec_, mediator_priv_key, publisher_pub_key, GetMediatorShareHkdfInfo(),
/*hkdf_salt=*/brillo::SecureBlob(), RecoveryCrypto::kHkdfHash,
kAesGcm256KeySize, &aes_gcm_key)) {
LOG(ERROR) << "Failed to generate ECDH+HKDF recipient key for HSM "
"plaintext decryption";
return false;
}
if (!AesGcmDecrypt(hsm_payload.cipher_text, hsm_payload.associated_data,
hsm_payload.tag, aes_gcm_key, hsm_payload.iv,
plain_text)) {
LOG(ERROR) << "Failed to perform AES-GCM decryption";
return false;
}
return true;
}
bool FakeRecoveryMediatorCrypto::DecryptRequestPayloadPlainText(
const brillo::SecureBlob& epoch_priv_key,
const RequestPayload& request_payload,
brillo::SecureBlob* plain_text) const {
brillo::SecureBlob salt;
if (!GetBytestringValueFromCborMapByKeyForTesting(
request_payload.associated_data, kRequestPayloadSalt, &salt)) {
LOG(ERROR) << "Unable to deserialize salt from request_payload";
return false;
}
HsmPayload hsm_payload;
if (!GetHsmPayloadFromRequestAdForTesting(request_payload.associated_data,
&hsm_payload)) {
LOG(ERROR) << "Unable to deserialize hsm_payload from request_payload";
return false;
}
brillo::SecureBlob channel_pub_key;
if (!GetBytestringValueFromCborMapByKeyForTesting(
hsm_payload.associated_data, kChannelPublicKey, &channel_pub_key)) {
LOG(ERROR) << "Unable to deserialize channel_pub_key from "
"hsm_payload.associated_data";
return false;
}
brillo::SecureBlob aes_gcm_key;
if (!GenerateEcdhHkdfRecipientKey(ec_, epoch_priv_key, channel_pub_key,
GetRequestPayloadPlainTextHkdfInfo(), salt,
RecoveryCrypto::kHkdfHash,
kAesGcm256KeySize, &aes_gcm_key)) {
LOG(ERROR) << "Failed to generate ECDH+HKDF recipient key for request "
"payload decryption";
return false;
}
if (!AesGcmDecrypt(request_payload.cipher_text,
request_payload.associated_data, request_payload.tag,
aes_gcm_key, request_payload.iv, plain_text)) {
LOG(ERROR) << "Failed to perform AES-GCM decryption of request_payload";
return false;
}
return true;
}
bool FakeRecoveryMediatorCrypto::MediateHsmPayload(
const brillo::SecureBlob& mediator_priv_key,
const brillo::SecureBlob& epoch_pub_key,
const brillo::SecureBlob& epoch_priv_key,
const brillo::SecureBlob& ephemeral_pub_inv_key,
const HsmPayload& hsm_payload,
brillo::SecureBlob* recovery_response_cbor) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
brillo::SecureBlob hsm_plain_text_cbor;
if (!DecryptHsmPayloadPlainText(mediator_priv_key, hsm_payload,
&hsm_plain_text_cbor)) {
LOG(ERROR) << "Unable to decrypt hsm_plain_text_cbor in hsm_payload";
return false;
}
HsmPlainText hsm_plain_text;
if (!DeserializeHsmPlainTextFromCbor(hsm_plain_text_cbor, &hsm_plain_text)) {
LOG(ERROR) << "Unable to deserialize hsm_plain_text_cbor";
return false;
}
crypto::ScopedBIGNUM mediator_share_bn =
SecureBlobToBigNum(hsm_plain_text.mediator_share);
if (!mediator_share_bn) {
LOG(ERROR) << "Failed to convert SecureBlob to BIGNUM";
return false;
}
crypto::ScopedEC_POINT dealer_pub_point =
ec_.SecureBlobToPoint(hsm_plain_text.dealer_pub_key, context.get());
if (!dealer_pub_point) {
LOG(ERROR) << "Failed to convert SecureBlob to EC_POINT";
return false;
}
// Performs scalar multiplication of dealer_pub_key and mediator_share.
brillo::SecureBlob mediator_dh;
crypto::ScopedEC_POINT mediator_dh_point =
ec_.Multiply(*dealer_pub_point, *mediator_share_bn, context.get());
if (!mediator_dh_point) {
LOG(ERROR) << "Failed to perform scalar multiplication";
return false;
}
// Perform addition of mediator_dh_point and ephemeral_pub_inv_key.
crypto::ScopedEC_POINT ephemeral_pub_inv_point =
ec_.SecureBlobToPoint(ephemeral_pub_inv_key, context.get());
if (!ephemeral_pub_inv_point) {
LOG(ERROR) << "Failed to convert SecureBlob to EC_POINT";
return false;
}
crypto::ScopedEC_POINT mediated_point =
ec_.Add(*mediator_dh_point, *ephemeral_pub_inv_point, context.get());
if (!mediated_point) {
LOG(ERROR) << "Failed to add mediator_dh_point and ephemeral_pub_inv_point";
return false;
}
if (!ec_.PointToSecureBlob(*mediated_point, &mediator_dh, context.get())) {
LOG(ERROR) << "Failed to convert EC_POINT to SecureBlob";
return false;
}
brillo::SecureBlob salt =
CreateSecureRandomBlob(RecoveryCrypto::kHkdfSaltLength);
ResponsePayload response_payload;
HsmResponseAssociatedData response_ad;
response_ad.response_meta_data = brillo::SecureBlob(kFakeHsmMetaData);
response_ad.response_payload_salt = salt;
if (!SerializeHsmResponseAssociatedDataToCbor(
response_ad, &response_payload.associated_data)) {
LOG(ERROR) << "Unable to serialize response payload associated data";
return false;
}
brillo::SecureBlob response_plain_text_cbor;
HsmResponsePlainText response_plain_text;
response_plain_text.mediated_point = mediator_dh;
response_plain_text.dealer_pub_key = hsm_plain_text.dealer_pub_key;
response_plain_text.key_auth_value = brillo::SecureBlob();
if (!SerializeHsmResponsePlainTextToCbor(response_plain_text,
&response_plain_text_cbor)) {
LOG(ERROR) << "Unable to serialize response plain text";
return false;
}
brillo::SecureBlob channel_pub_key;
if (!GetBytestringValueFromCborMapByKeyForTesting(
hsm_payload.associated_data, kChannelPublicKey, &channel_pub_key)) {
LOG(ERROR) << "Unable to deserialize channel_pub_key from hsm_payload";
return false;
}
brillo::SecureBlob aes_gcm_key;
// The static nature of `channel_pub_key` (G*s) and `epoch_pub_key` (G*r)
// requires the need to utilize a randomized salt value in the HKDF
// computation.
if (!GenerateEcdhHkdfSenderKey(
ec_, channel_pub_key, epoch_pub_key, epoch_priv_key,
GetResponsePayloadPlainTextHkdfInfo(), salt,
RecoveryCrypto::kHkdfHash, kAesGcm256KeySize, &aes_gcm_key)) {
LOG(ERROR)
<< "Failed to generate ECDH+HKDF recipient key for Recovery Request "
"plaintext encryption";
return false;
}
if (!AesGcmEncrypt(response_plain_text_cbor, response_payload.associated_data,
aes_gcm_key, &response_payload.iv, &response_payload.tag,
&response_payload.cipher_text)) {
LOG(ERROR) << "Failed to perform AES-GCM encryption of response_payload";
return false;
}
RecoveryResponse recovery_response;
recovery_response.response_payload = std::move(response_payload);
recovery_response.error_code = 0;
if (!SerializeRecoveryResponseToCbor(recovery_response,
recovery_response_cbor)) {
LOG(ERROR) << "Failed to serialize Recovery Response to cbor";
return false;
}
return true;
}
bool FakeRecoveryMediatorCrypto::MediateRequestPayload(
const brillo::SecureBlob& epoch_pub_key,
const brillo::SecureBlob& epoch_priv_key,
const brillo::SecureBlob& mediator_priv_key,
const brillo::SecureBlob& recovery_request_cbor,
brillo::SecureBlob* recovery_response_cbor) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
RecoveryRequest recovery_request;
if (!DeserializeRecoveryRequestFromCbor(recovery_request_cbor,
&recovery_request)) {
LOG(ERROR) << "Unable to deserialize Recovery Request";
return false;
}
brillo::SecureBlob request_plain_text_cbor;
if (!DecryptRequestPayloadPlainText(epoch_priv_key,
recovery_request.request_payload,
&request_plain_text_cbor)) {
LOG(ERROR) << "Unable to decrypt plain text in request_payload";
return false;
}
RecoveryRequestPlainText plain_text;
if (!DeserializeRecoveryRequestPlainTextFromCbor(request_plain_text_cbor,
&plain_text)) {
LOG(ERROR)
<< "Unable to deserialize Recovery Request request_plain_text_cbor";
return false;
}
HsmPayload hsm_payload;
if (!GetHsmPayloadFromRequestAdForTesting(
recovery_request.request_payload.associated_data, &hsm_payload)) {
LOG(ERROR) << "Unable to extract hsm_payload from request_payload";
return false;
}
return MediateHsmPayload(mediator_priv_key, epoch_pub_key, epoch_priv_key,
plain_text.ephemeral_pub_inv_key, hsm_payload,
recovery_response_cbor);
}
} // namespace cryptorecovery
} // namespace cryptohome