blob: 999dd5f91ca9fe1d9f871fdad44cbf5d5b83cf8a [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 <optional>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <base/stl_util.h>
#include <brillo/secure_blob.h>
#include <libhwsec-foundation/crypto/aes.h>
#include <libhwsec-foundation/crypto/big_num_util.h>
#include <libhwsec-foundation/crypto/ecdh_hkdf.h>
#include <libhwsec-foundation/crypto/elliptic_curve.h>
#include <libhwsec-foundation/crypto/error_util.h>
#include <libhwsec-foundation/crypto/rsa.h>
#include <libhwsec-foundation/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"
using ::hwsec_foundation::AesGcmDecrypt;
using ::hwsec_foundation::AesGcmEncrypt;
using ::hwsec_foundation::CreateBigNumContext;
using ::hwsec_foundation::CreateSecureRandomBlob;
using ::hwsec_foundation::EllipticCurve;
using ::hwsec_foundation::GenerateEcdhHkdfSymmetricKey;
using ::hwsec_foundation::kAesGcm256KeySize;
using ::hwsec_foundation::ScopedBN_CTX;
using ::hwsec_foundation::SecureBlobToBigNum;
using ::hwsec_foundation::VerifyRsaSignatureSha256;
namespace cryptohome {
namespace cryptorecovery {
namespace {
brillo::SecureBlob GetMediatorShareHkdfInfo() {
return brillo::SecureBlob(RecoveryCrypto::kMediatorShareHkdfInfoValue);
}
brillo::SecureBlob GetRequestPayloadPlainTextHkdfInfo() {
return brillo::SecureBlob(
RecoveryCrypto::kRequestPayloadPlainTextHkdfInfoValue);
}
brillo::SecureBlob GetResponsePayloadPlainTextHkdfInfo() {
return brillo::SecureBlob(
RecoveryCrypto::kResponsePayloadPlainTextHkdfInfoValue);
}
bool GetRecoveryRequestFromProto(
const CryptoRecoveryRpcRequest& recovery_request_proto,
RecoveryRequest* recovery_request) {
if (!recovery_request_proto.has_cbor_cryptorecoveryrequest()) {
LOG(ERROR)
<< "No cbor_cryptorecoveryrequest field in recovery_request_proto";
return false;
}
brillo::SecureBlob recovery_request_cbor(
recovery_request_proto.cbor_cryptorecoveryrequest().begin(),
recovery_request_proto.cbor_cryptorecoveryrequest().end());
if (!DeserializeRecoveryRequestFromCbor(recovery_request_cbor,
recovery_request)) {
LOG(ERROR) << "Unable to deserialize Recovery Request";
return false;
}
return true;
}
bool GenerateRecoveryRequestProto(
const RecoveryResponse& response,
CryptoRecoveryRpcResponse* recovery_response) {
brillo::SecureBlob recovery_response_cbor;
if (!SerializeRecoveryResponseToCbor(response, &recovery_response_cbor)) {
LOG(ERROR) << "Failed to serialize Recovery Response to cbor";
return false;
}
recovery_response->set_protocol_version(1);
recovery_response->set_cbor_cryptorecoveryresponse(
recovery_response_cbor.data(), recovery_response_cbor.size());
return true;
}
} // 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;
}
std::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;
}
// static
bool FakeRecoveryMediatorCrypto::GetFakeEpochResponse(
CryptoRecoveryEpochResponse* epoch_response) {
brillo::SecureBlob epoch_pub_key;
if (!GetFakeEpochPublicKey(&epoch_pub_key)) {
LOG(ERROR) << "Failed to get fake epoch public key";
return false;
}
brillo::SecureBlob epoch_metadata_cbor;
cbor::Value::MapValue meta_data_cbor;
meta_data_cbor.emplace("meta_data_cbor_key", "meta_data_cbor_value");
if (!SerializeCborForTesting(cbor::Value(meta_data_cbor),
&epoch_metadata_cbor)) {
LOG(ERROR) << "Failed to create epoch_metadata_cbor";
return false;
}
epoch_response->set_protocol_version(1);
epoch_response->set_epoch_pub_key(epoch_pub_key.data(), epoch_pub_key.size());
epoch_response->set_epoch_meta_data(epoch_metadata_cbor.data(),
epoch_metadata_cbor.size());
return true;
}
bool FakeRecoveryMediatorCrypto::DecryptMediatorShare(
const brillo::SecureBlob& mediator_priv_key,
const RecoveryCrypto::EncryptedMediatorShare& encrypted_mediator_share,
brillo::SecureBlob* mediator_share) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
crypto::ScopedBIGNUM mediator_priv_key_bn =
SecureBlobToBigNum(mediator_priv_key);
if (!mediator_priv_key_bn) {
LOG(ERROR) << "Failed to convert mediator_priv_key to BIGNUM";
return false;
}
crypto::ScopedEC_POINT ephemeral_pub_key = ec_.SecureBlobToPoint(
encrypted_mediator_share.ephemeral_pub_key, context.get());
if (!ephemeral_pub_key) {
LOG(ERROR) << "Failed to convert ephemeral_pub_key SecureBlob to EC_POINT";
return false;
}
crypto::ScopedEC_POINT shared_secret_point = ComputeEcdhSharedSecretPoint(
ec_, *ephemeral_pub_key, *mediator_priv_key_bn);
if (!shared_secret_point) {
LOG(ERROR) << "Failed to compute shared_secret_point";
return false;
}
brillo::SecureBlob aes_gcm_key;
if (!GenerateEcdhHkdfSymmetricKey(
ec_, *shared_secret_point, 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=*/std::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 {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
brillo::SecureBlob publisher_pub_key_blob;
if (!GetBytestringValueFromCborMapByKeyForTesting(hsm_payload.associated_data,
kPublisherPublicKey,
&publisher_pub_key_blob)) {
LOG(ERROR) << "Unable to deserialize publisher_pub_key from hsm_payload";
return false;
}
crypto::ScopedBIGNUM mediator_priv_key_bn =
SecureBlobToBigNum(mediator_priv_key);
if (!mediator_priv_key_bn) {
LOG(ERROR) << "Failed to convert mediator_priv_key to BIGNUM";
return false;
}
crypto::ScopedEC_POINT publisher_pub_key =
ec_.SecureBlobToPoint(publisher_pub_key_blob, context.get());
if (!publisher_pub_key) {
LOG(ERROR) << "Failed to convert publisher_pub_key_blob to EC_POINT";
return false;
}
crypto::ScopedEC_POINT shared_secret_point = ComputeEcdhSharedSecretPoint(
ec_, *publisher_pub_key, *mediator_priv_key_bn);
if (!shared_secret_point) {
LOG(ERROR) << "Failed to compute shared_secret_point";
return false;
}
brillo::SecureBlob aes_gcm_key;
if (!GenerateEcdhHkdfSymmetricKey(
ec_, *shared_secret_point, publisher_pub_key_blob,
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 {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
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_blob;
if (!GetBytestringValueFromCborMapByKeyForTesting(hsm_payload.associated_data,
kChannelPublicKey,
&channel_pub_key_blob)) {
LOG(ERROR) << "Unable to deserialize channel_pub_key from "
"hsm_payload.associated_data";
return false;
}
crypto::ScopedBIGNUM epoch_priv_key_bn = SecureBlobToBigNum(epoch_priv_key);
if (!epoch_priv_key_bn) {
LOG(ERROR) << "Failed to convert epoch_priv_key to BIGNUM";
return false;
}
crypto::ScopedEC_POINT channel_pub_key =
ec_.SecureBlobToPoint(channel_pub_key_blob, context.get());
if (!channel_pub_key) {
LOG(ERROR) << "Failed to convert channel_pub_key_blob to EC_POINT";
return false;
}
crypto::ScopedEC_POINT shared_secret_point =
ComputeEcdhSharedSecretPoint(ec_, *channel_pub_key, *epoch_priv_key_bn);
if (!shared_secret_point) {
LOG(ERROR) << "Failed to compute shared_secret_point";
return false;
}
brillo::SecureBlob aes_gcm_key;
if (!GenerateEcdhHkdfSymmetricKey(
ec_, *shared_secret_point, channel_pub_key_blob,
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,
CryptoRecoveryRpcResponse* recovery_response_proto) 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_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 = hsm_plain_text.key_auth_value;
if (!SerializeHsmResponsePlainTextToCbor(response_plain_text,
&response_plain_text_cbor)) {
LOG(ERROR) << "Unable to serialize response plain text";
return false;
}
brillo::SecureBlob channel_pub_key_blob;
if (!GetBytestringValueFromCborMapByKeyForTesting(hsm_payload.associated_data,
kChannelPublicKey,
&channel_pub_key_blob)) {
LOG(ERROR) << "Unable to deserialize channel_pub_key from hsm_payload";
return false;
}
crypto::ScopedBIGNUM epoch_priv_key_bn = SecureBlobToBigNum(epoch_priv_key);
if (!epoch_priv_key_bn) {
LOG(ERROR) << "Failed to convert epoch_priv_key to BIGNUM";
return false;
}
crypto::ScopedEC_POINT channel_pub_key =
ec_.SecureBlobToPoint(channel_pub_key_blob, context.get());
if (!channel_pub_key) {
LOG(ERROR) << "Failed to convert channel_pub_key_blob to EC_POINT";
return false;
}
crypto::ScopedEC_POINT shared_secret_point =
ComputeEcdhSharedSecretPoint(ec_, *channel_pub_key, *epoch_priv_key_bn);
if (!shared_secret_point) {
LOG(ERROR) << "Failed to compute shared_secret_point";
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 (!GenerateEcdhHkdfSymmetricKey(ec_, *shared_secret_point, epoch_pub_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 (!GenerateRecoveryRequestProto(recovery_response,
recovery_response_proto)) {
LOG(ERROR) << "Failed to generate Recovery Response proto";
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 CryptoRecoveryRpcRequest& recovery_request_proto,
CryptoRecoveryRpcResponse* recovery_response_proto) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
// Parse out the rsa_signature in Recovery Request
RecoveryRequest recovery_request;
if (!GetRecoveryRequestFromProto(recovery_request_proto, &recovery_request)) {
LOG(ERROR) << "Couldn't get recovery request from recovery_request_proto";
return false;
}
// Parse out the rsa_public_key, which is in Hsm Associated Data. Hsm
// Associated Data is in Hsm Payload, and it is in the Associated Data of
// Request Payload
RequestPayload request_payload;
if (!DeserializeRecoveryRequestPayloadFromCbor(
recovery_request.request_payload, &request_payload)) {
LOG(ERROR) << "Failed to deserialize Request payload.";
return false;
}
HsmPayload hsm_payload;
if (!GetHsmPayloadFromRequestAdForTesting(request_payload.associated_data,
&hsm_payload)) {
LOG(ERROR) << "Unable to extract hsm_payload from request_payload";
return false;
}
HsmAssociatedData hsm_associated_data;
if (!DeserializeHsmAssociatedDataFromCbor(hsm_payload.associated_data,
&hsm_associated_data)) {
LOG(ERROR) << "Unable to deserialize hsm_associated_data_cbor";
return false;
}
// If the recovery request is sent from devices with TPM2.0, no RSA signature
// is attached to be verified and the public key wrapped in AD1 would be
// empty.
if (!hsm_associated_data.rsa_public_key.empty() ||
!recovery_request.rsa_signature.empty()) {
// Verify RSA signature with RSA public key and request payload
if (!VerifyRsaSignatureSha256(recovery_request.request_payload,
recovery_request.rsa_signature,
hsm_associated_data.rsa_public_key)) {
LOG(ERROR)
<< "Unable to initiate verifying rsa signature in request_payload";
return false;
}
}
brillo::SecureBlob request_plain_text_cbor;
if (!DecryptRequestPayloadPlainText(epoch_priv_key, 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;
}
if (!MediateHsmPayload(mediator_priv_key, epoch_pub_key, epoch_priv_key,
plain_text.ephemeral_pub_inv_key, hsm_payload,
recovery_response_proto)) {
LOG(ERROR) << "Unable to mediate hsm_payload";
return false;
}
return true;
}
} // namespace cryptorecovery
} // namespace cryptohome