blob: d914d567cae24bdfaeb25007029b391a897c92fc [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/recovery_crypto_impl.h"
#include <algorithm>
#include <cstddef>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/logging.h>
#include <base/memory/ptr_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/error_util.h>
#include <libhwsec-foundation/crypto/rsa.h>
#include <libhwsec-foundation/crypto/secure_blob_util.h>
#include <openssl/ec.h>
#include "cryptohome/cryptohome_common.h"
#include "cryptohome/cryptorecovery/recovery_crypto_hsm_cbor_serialization.h"
using ::hwsec_foundation::AesGcmDecrypt;
using ::hwsec_foundation::AesGcmEncrypt;
using ::hwsec_foundation::BigNumToSecureBlob;
using ::hwsec_foundation::CreateBigNum;
using ::hwsec_foundation::CreateBigNumContext;
using ::hwsec_foundation::CreateSecureRandomBlob;
using ::hwsec_foundation::EllipticCurve;
using ::hwsec_foundation::GenerateEcdhHkdfSymmetricKey;
using ::hwsec_foundation::HkdfHash;
using ::hwsec_foundation::kAesGcm256KeySize;
using ::hwsec_foundation::ScopedBN_CTX;
using ::hwsec_foundation::SecureBlobToBigNum;
namespace cryptohome {
namespace cryptorecovery {
namespace {
brillo::SecureBlob GetRecoveryKeyHkdfInfo() {
return brillo::SecureBlob("CryptoHome Wrapping Key");
}
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 GenerateRecoveryRequestAssociatedData(
const HsmPayload& hsm_payload,
const RequestMetadata& request_meta_data,
const CryptoRecoveryEpochResponse& epoch_response,
RecoveryRequestAssociatedData* request_ad) {
request_ad->hsm_payload = hsm_payload;
request_ad->request_meta_data = request_meta_data;
if (!epoch_response.has_epoch_meta_data()) {
LOG(ERROR) << "Epoch response doesn't have epoch metadata";
return false;
}
brillo::SecureBlob epoch_meta_data(epoch_response.epoch_meta_data().begin(),
epoch_response.epoch_meta_data().end());
if (!DeserializeEpochMetadataFromCbor(epoch_meta_data,
&request_ad->epoch_meta_data)) {
LOG(ERROR) << "Failed to deserialize epoch metadata from cbor";
return false;
}
if (!epoch_response.has_epoch_pub_key()) {
LOG(ERROR) << "Epoch response doesn't have epoch public key";
return false;
}
request_ad->epoch_pub_key.assign(epoch_response.epoch_pub_key().begin(),
epoch_response.epoch_pub_key().end());
request_ad->request_payload_salt =
CreateSecureRandomBlob(RecoveryCrypto::kHkdfSaltLength);
return true;
}
bool GenerateRecoveryRequestProto(const RecoveryRequest& request,
CryptoRecoveryRpcRequest* recovery_request) {
brillo::SecureBlob request_cbor;
if (!SerializeRecoveryRequestToCbor(request, &request_cbor)) {
LOG(ERROR) << "Failed to serialize Recovery Request to CBOR";
return false;
}
recovery_request->set_protocol_version(1);
recovery_request->set_cbor_cryptorecoveryrequest(request_cbor.data(),
request_cbor.size());
return true;
}
bool GetRecoveryResponseFromProto(
const CryptoRecoveryRpcResponse& recovery_response_proto,
RecoveryResponse* recovery_response) {
if (!recovery_response_proto.has_cbor_cryptorecoveryresponse()) {
LOG(ERROR)
<< "No cbor_cryptorecoveryresponse field in recovery_response_proto";
return false;
}
brillo::SecureBlob recovery_response_cbor(
recovery_response_proto.cbor_cryptorecoveryresponse().begin(),
recovery_response_proto.cbor_cryptorecoveryresponse().end());
if (!DeserializeRecoveryResponseFromCbor(recovery_response_cbor,
recovery_response)) {
LOG(ERROR) << "Unable to deserialize Recovery Response from CBOR";
return false;
}
return true;
}
} // namespace
std::unique_ptr<RecoveryCryptoImpl> RecoveryCryptoImpl::Create(
RecoveryCryptoTpmBackend* tpm_backend) {
DCHECK(tpm_backend);
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return nullptr;
}
std::optional<EllipticCurve> ec =
EllipticCurve::Create(kCurve, context.get());
if (!ec) {
LOG(ERROR) << "Failed to create EllipticCurve";
return nullptr;
}
// using wrapUnique because the constructor is private and make_unique calls
// the constructor externally
return base::WrapUnique(new RecoveryCryptoImpl(std::move(*ec), tpm_backend));
}
RecoveryCryptoImpl::RecoveryCryptoImpl(EllipticCurve ec,
RecoveryCryptoTpmBackend* tpm_backend)
: ec_(std::move(ec)), tpm_backend_(tpm_backend) {
DCHECK(tpm_backend_);
}
RecoveryCryptoImpl::~RecoveryCryptoImpl() = default;
bool RecoveryCryptoImpl::GenerateRecoveryKey(
const crypto::ScopedEC_POINT& recovery_pub_point,
const crypto::ScopedEC_KEY& dealer_key_pair,
brillo::SecureBlob* recovery_key) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
const BIGNUM* dealer_priv_key =
EC_KEY_get0_private_key(dealer_key_pair.get());
if (!dealer_priv_key) {
LOG(ERROR) << "Failed to get dealer_priv_key";
return false;
}
crypto::ScopedEC_POINT point_dh =
ec_.Multiply(*recovery_pub_point, *dealer_priv_key, context.get());
if (!point_dh) {
LOG(ERROR) << "Failed to perform point multiplication of "
"recovery_pub_point and dealer_priv_key";
return false;
}
// Get point's affine X coordinate.
crypto::ScopedBIGNUM recovery_dh_x = CreateBigNum();
if (!recovery_dh_x) {
LOG(ERROR) << "Failed to allocate BIGNUM";
return false;
}
if (!ec_.GetAffineCoordinates(*point_dh, context.get(), recovery_dh_x.get(),
/*y=*/nullptr)) {
LOG(ERROR) << "Failed to get point_dh x coordinate";
return false;
}
brillo::SecureBlob hkdf_secret;
// Convert X coordinate to fixed-size blob.
if (!BigNumToSecureBlob(*recovery_dh_x, ec_.AffineCoordinateSizeInBytes(),
&hkdf_secret)) {
LOG(ERROR) << "Failed to convert recovery_dh_x BIGNUM to SecureBlob";
return false;
}
const EC_POINT* dealer_pub_point =
EC_KEY_get0_public_key(dealer_key_pair.get());
if (!dealer_pub_point) {
LOG(ERROR) << "Failed to get dealer_pub_point";
return false;
}
brillo::SecureBlob dealer_pub_key;
if (!ec_.PointToSecureBlob(*dealer_pub_point, &dealer_pub_key,
context.get())) {
LOG(ERROR) << "Failed to convert dealer_pub_key to a SecureBlob";
return false;
}
if (!ComputeHkdfWithInfoSuffix(hkdf_secret, GetRecoveryKeyHkdfInfo(),
dealer_pub_key, /*salt=*/brillo::SecureBlob(),
HkdfHash::kSha256, /*result_len=*/0,
recovery_key)) {
LOG(ERROR) << "Failed to compute HKDF of recovery_dh";
return false;
}
return true;
}
bool RecoveryCryptoImpl::GenerateEphemeralKey(
brillo::SecureBlob* ephemeral_pub_key,
brillo::SecureBlob* ephemeral_inv_pub_key) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
// Generate ephemeral key pair {`ephemeral_secret`, `ephemeral_pub_key`} ({x,
// G*x}), and the inverse public key G*-x.
crypto::ScopedBIGNUM ephemeral_priv_key_bn =
ec_.RandomNonZeroScalar(context.get());
if (!ephemeral_priv_key_bn) {
LOG(ERROR) << "Failed to generate ephemeral_priv_key_bn";
return false;
}
crypto::ScopedEC_POINT ephemeral_pub_point =
ec_.MultiplyWithGenerator(*ephemeral_priv_key_bn, context.get());
if (!ephemeral_pub_point) {
LOG(ERROR) << "Failed to perform MultiplyWithGenerator operation for "
"ephemeral_priv_key_bn";
return false;
}
if (!ec_.PointToSecureBlob(*ephemeral_pub_point, ephemeral_pub_key,
context.get())) {
LOG(ERROR) << "Failed to convert ephemeral_pub_point to a SecureBlob";
return false;
}
if (!ec_.InvertPoint(ephemeral_pub_point.get(), context.get())) {
LOG(ERROR) << "Failed to invert the ephemeral_pub_point";
return false;
}
if (!ec_.PointToSecureBlob(*ephemeral_pub_point, ephemeral_inv_pub_key,
context.get())) {
LOG(ERROR)
<< "Failed to convert inverse ephemeral_pub_point to a SecureBlob";
return false;
}
return true;
}
bool RecoveryCryptoImpl::GenerateRecoveryRequest(
const HsmPayload& hsm_payload,
const RequestMetadata& request_meta_data,
const CryptoRecoveryEpochResponse& epoch_response,
const brillo::SecureBlob& encrypted_rsa_priv_key,
const brillo::SecureBlob& encrypted_channel_priv_key,
const brillo::SecureBlob& channel_pub_key,
CryptoRecoveryRpcRequest* recovery_request,
brillo::SecureBlob* ephemeral_pub_key) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
RequestPayload request_payload;
RecoveryRequestAssociatedData request_ad;
if (!GenerateRecoveryRequestAssociatedData(hsm_payload, request_meta_data,
epoch_response, &request_ad)) {
LOG(ERROR) << "Failed to generate recovery request associated data";
return false;
}
if (!SerializeRecoveryRequestAssociatedDataToCbor(
request_ad, &request_payload.associated_data)) {
LOG(ERROR)
<< "Failed to serialize recovery request associated data to cbor";
return false;
}
brillo::SecureBlob epoch_pub_key;
epoch_pub_key.assign(epoch_response.epoch_pub_key().begin(),
epoch_response.epoch_pub_key().end());
crypto::ScopedEC_POINT epoch_pub_point =
ec_.SecureBlobToPoint(epoch_pub_key, context.get());
if (!epoch_pub_point) {
LOG(ERROR) << "Failed to convert epoch_pub_key SecureBlob to EC_POINT";
return false;
}
// Performs scalar multiplication of epoch_pub_point and channel_priv_key.
// Here we don't use key_auth_value generated from GenerateKeyAuthValue()
// because key_auth_value is unavailable and will only be recovered from the
// decrypted response afterward
crypto::ScopedEC_POINT shared_secret_point =
tpm_backend_->GenerateDiffieHellmanSharedSecret(
ec_, encrypted_channel_priv_key, /*auth_value=*/std::nullopt,
*epoch_pub_point);
if (!shared_secret_point) {
LOG(ERROR) << "Failed to compute shared point from epoch_pub_point and "
"channel_priv_key";
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, channel_pub_key,
GetRequestPayloadPlainTextHkdfInfo(),
request_ad.request_payload_salt, kHkdfHash,
kAesGcm256KeySize, &aes_gcm_key)) {
LOG(ERROR) << "Failed to generate ECDH+HKDF sender keys for recovery "
"request plain text encryption";
return false;
}
// Dispose shared_secret_point after used
shared_secret_point.reset();
brillo::SecureBlob ephemeral_inverse_pub_key;
if (!GenerateEphemeralKey(ephemeral_pub_key, &ephemeral_inverse_pub_key)) {
LOG(ERROR) << "Failed to generate Ephemeral keys";
return false;
}
brillo::SecureBlob plain_text_cbor;
RecoveryRequestPlainText plain_text;
plain_text.ephemeral_pub_inv_key = ephemeral_inverse_pub_key;
if (!SerializeRecoveryRequestPlainTextToCbor(plain_text, &plain_text_cbor)) {
LOG(ERROR) << "Failed to generate Recovery Request plain text cbor";
return false;
}
if (!AesGcmEncrypt(plain_text_cbor, request_payload.associated_data,
aes_gcm_key, &request_payload.iv, &request_payload.tag,
&request_payload.cipher_text)) {
LOG(ERROR) << "Failed to perform AES-GCM encryption of plain_text_cbor for "
"recovery request";
return false;
}
// Sign the request payload with the rsa private key
brillo::SecureBlob request_payload_blob;
if (!SerializeRecoveryRequestPayloadToCbor(request_payload,
&request_payload_blob)) {
LOG(ERROR) << "Failed to serialize Recovery Request payload";
return false;
}
brillo::SecureBlob rsa_signature;
if (!tpm_backend_->SignRequestPayload(encrypted_rsa_priv_key,
request_payload_blob, &rsa_signature)) {
LOG(ERROR) << "Failed to sign Recovery Request payload";
return false;
}
RecoveryRequest request;
request.request_payload = std::move(request_payload_blob);
request.rsa_signature = std::move(rsa_signature);
if (!GenerateRecoveryRequestProto(request, recovery_request)) {
LOG(ERROR) << "Failed to generate Recovery Request proto";
return false;
}
return true;
}
bool RecoveryCryptoImpl::GenerateHsmPayload(
const brillo::SecureBlob& mediator_pub_key,
const OnboardingMetadata& onboarding_metadata,
HsmPayload* hsm_payload,
brillo::SecureBlob* encrypted_rsa_priv_key,
brillo::SecureBlob* encrypted_destination_share,
brillo::SecureBlob* recovery_key,
brillo::SecureBlob* channel_pub_key,
brillo::SecureBlob* encrypted_channel_priv_key) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
// Generate two shares and a secret equal to the sum.
// Loop until the sum of two shares is non-zero (modulo order).
crypto::ScopedEC_KEY destination_share_key_pair =
ec_.GenerateKey(context.get());
if (!destination_share_key_pair) {
LOG(ERROR) << "Failed to generate destination share key pair";
return false;
}
const BIGNUM* destination_share_bn =
EC_KEY_get0_private_key(destination_share_key_pair.get());
if (!destination_share_bn) {
LOG(ERROR) << "Failed to generate destination_share secret";
return false;
}
crypto::ScopedBIGNUM secret;
crypto::ScopedBIGNUM mediator_share_bn;
do {
mediator_share_bn = ec_.RandomNonZeroScalar(context.get());
if (!mediator_share_bn) {
LOG(ERROR) << "Failed to generate mediator_share secret";
return false;
}
secret =
ec_.ModAdd(*mediator_share_bn, *destination_share_bn, context.get());
if (!secret) {
LOG(ERROR) << "Failed to perform modulo addition of mediator_share "
"and destination_share";
return false;
}
} while (BN_is_zero(secret.get()));
brillo::SecureBlob key_auth_value = tpm_backend_->GenerateKeyAuthValue();
if (!tpm_backend_->EncryptEccPrivateKey(ec_, destination_share_key_pair,
key_auth_value,
encrypted_destination_share)) {
LOG(ERROR) << "Failed to encrypt destination share";
return false;
}
crypto::ScopedEC_POINT recovery_pub_point =
ec_.MultiplyWithGenerator(*secret, context.get());
if (!recovery_pub_point) {
LOG(ERROR)
<< "Failed to perform MultiplyWithGenerator operation for the secret";
return false;
}
// Generate RSA key pair
brillo::SecureBlob rsa_public_key_der;
if (!tpm_backend_->GenerateRsaKeyPair(encrypted_rsa_priv_key,
&rsa_public_key_der)) {
LOG(ERROR) << "Error creating PCR bound signing key.";
return false;
}
// Generate channel key pair.
crypto::ScopedEC_KEY channel_key_pair = ec_.GenerateKey(context.get());
if (!channel_key_pair) {
LOG(ERROR) << "Failed to generate channel key pair";
return false;
}
const EC_POINT* channel_pub_point =
EC_KEY_get0_public_key(channel_key_pair.get());
if (!channel_pub_point) {
LOG(ERROR) << "Failed to get channel_pub_point";
return false;
}
if (!ec_.PointToSecureBlob(*channel_pub_point, channel_pub_key,
context.get())) {
LOG(ERROR) << "Failed to convert channel_pub_key to a SecureBlob";
return false;
}
// Here we don't use key_auth_value generated from GenerateKeyAuthValue()
// because key_auth_value will be unavailable when encrypted_channel_priv_key
// is unsealed from TPM1.2
if (!tpm_backend_->EncryptEccPrivateKey(ec_, channel_key_pair,
/*auth_value=*/std::nullopt,
encrypted_channel_priv_key)) {
LOG(ERROR) << "Failed to encrypt channel_priv_key";
return false;
}
crypto::ScopedEC_KEY publisher_key_pair = ec_.GenerateKey(context.get());
if (!publisher_key_pair) {
LOG(ERROR) << "Failed to generate publisher key pair";
return false;
}
// Construct associated data for HSM payload: AD = CBOR({publisher_pub_key,
// channel_pub_key, rsa_pub_key, onboarding_metadata}).
if (!GenerateHsmAssociatedData(*channel_pub_key, rsa_public_key_der,
publisher_key_pair, onboarding_metadata,
&hsm_payload->associated_data)) {
LOG(ERROR) << "Failed to generate HSM associated data cbor";
return false;
}
// Generate dealer key pair.
crypto::ScopedEC_KEY dealer_key_pair = ec_.GenerateKey(context.get());
if (!dealer_key_pair) {
LOG(ERROR) << "Failed to generate dealer key pair";
return false;
}
// Construct plain text for HSM payload PT = CBOR({dealer_pub_key,
// mediator_share, kav}).
const EC_POINT* dealer_pub_point =
EC_KEY_get0_public_key(dealer_key_pair.get());
if (!dealer_pub_point) {
LOG(ERROR) << "Failed to get dealer_pub_point";
return false;
}
brillo::SecureBlob dealer_pub_key;
if (!ec_.PointToSecureBlob(*dealer_pub_point, &dealer_pub_key,
context.get())) {
LOG(ERROR) << "Failed to convert dealer_pub_key to a SecureBlob";
return false;
}
brillo::SecureBlob mediator_share;
if (!BigNumToSecureBlob(*mediator_share_bn, ec_.ScalarSizeInBytes(),
&mediator_share)) {
LOG(ERROR) << "Failed to convert mediator_share to a SecureBlob";
return false;
}
brillo::SecureBlob plain_text_cbor;
HsmPlainText hsm_plain_text;
hsm_plain_text.mediator_share = mediator_share;
hsm_plain_text.dealer_pub_key = dealer_pub_key;
hsm_plain_text.key_auth_value = key_auth_value;
if (!SerializeHsmPlainTextToCbor(hsm_plain_text, &plain_text_cbor)) {
LOG(ERROR) << "Failed to generate HSM plain text cbor";
return false;
}
// Generate symmetric key for encrypting PT from (G*h)*u (where G*h is the
// mediator public key provided as input, and u is the publisher private key).
crypto::ScopedEC_POINT mediator_pub_point =
ec_.SecureBlobToPoint(mediator_pub_key, context.get());
if (!mediator_pub_point) {
LOG(ERROR) << "Failed to convert mediator_pub_key to EC_POINT";
return false;
}
const BIGNUM* publisher_priv_key =
EC_KEY_get0_private_key(publisher_key_pair.get());
if (!publisher_priv_key) {
LOG(ERROR) << "Failed to get publisher_priv_key from publisher_key_pair";
return false;
}
crypto::ScopedEC_POINT shared_secret_point = ComputeEcdhSharedSecretPoint(
ec_, *mediator_pub_point, *publisher_priv_key);
if (!shared_secret_point) {
LOG(ERROR) << "Failed to compute shared point from mediator_pub_point and "
"publisher_priv_key";
return false;
}
const EC_POINT* publisher_pub_key =
EC_KEY_get0_public_key(publisher_key_pair.get());
if (!publisher_pub_key) {
LOG(ERROR) << "Failed to get publisher_pub_key from publisher_key_pair";
return false;
}
brillo::SecureBlob publisher_pub_key_blob;
if (!ec_.PointToSecureBlob(*publisher_pub_key, &publisher_pub_key_blob,
context.get())) {
LOG(ERROR) << "Failed to convert publisher_pub_key to a SecureBlob";
return false;
}
brillo::SecureBlob aes_gcm_key;
// |hkdf_salt| can be empty here because the input already has a high entropy.
// Bruteforce attacks are not an issue here and as we generate an ephemeral
// key as input to HKDF the output will already be non-deterministic.
if (!GenerateEcdhHkdfSymmetricKey(
ec_, *shared_secret_point, publisher_pub_key_blob,
GetMediatorShareHkdfInfo(),
/*hkdf_salt=*/brillo::SecureBlob(), kHkdfHash, kAesGcm256KeySize,
&aes_gcm_key)) {
LOG(ERROR) << "Failed to generate ECDH+HKDF sender keys for HSM plain text "
"encryption";
return false;
}
if (!AesGcmEncrypt(plain_text_cbor, hsm_payload->associated_data, aes_gcm_key,
&hsm_payload->iv, &hsm_payload->tag,
&hsm_payload->cipher_text)) {
LOG(ERROR) << "Failed to perform AES-GCM encryption of HSM plain text";
return false;
}
// Cleanup: all intermediate secrets must be securely disposed at the end of
// HSM payload generation.
aes_gcm_key.clear();
shared_secret_point.reset();
plain_text_cbor.clear();
mediator_share.clear();
dealer_pub_key.clear();
publisher_key_pair.reset();
GenerateRecoveryKey(recovery_pub_point, dealer_key_pair, recovery_key);
return true;
}
bool RecoveryCryptoImpl::RecoverDestination(
const brillo::SecureBlob& dealer_pub_key,
const brillo::SecureBlob& key_auth_value,
const brillo::SecureBlob& encrypted_destination_share,
const brillo::SecureBlob& ephemeral_pub_key,
const brillo::SecureBlob& mediated_publisher_pub_key,
brillo::SecureBlob* destination_recovery_key) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
crypto::ScopedEC_POINT dealer_pub_point =
ec_.SecureBlobToPoint(dealer_pub_key, context.get());
if (!dealer_pub_point) {
LOG(ERROR) << "Failed to convert dealer_pub_point SecureBlob to EC_POINT";
return false;
}
crypto::ScopedEC_POINT mediated_point =
ec_.SecureBlobToPoint(mediated_publisher_pub_key, context.get());
if (!mediated_point) {
LOG(ERROR) << "Failed to convert mediated_point SecureBlob to EC_POINT";
return false;
}
// Performs addition of mediated_point and ephemeral_pub_point.
crypto::ScopedEC_POINT ephemeral_pub_point =
ec_.SecureBlobToPoint(ephemeral_pub_key, context.get());
if (!ephemeral_pub_point) {
LOG(ERROR)
<< "Failed to convert ephemeral_pub_point SecureBlob to EC_POINT";
return false;
}
crypto::ScopedEC_POINT mediator_dh =
ec_.Add(*mediated_point, *ephemeral_pub_point, context.get());
if (!mediator_dh) {
LOG(ERROR) << "Failed to add mediated_point and ephemeral_pub_point";
return false;
}
// Performs scalar multiplication of dealer_pub_point and destination_share.
crypto::ScopedEC_POINT point_dh =
tpm_backend_->GenerateDiffieHellmanSharedSecret(
ec_, encrypted_destination_share, key_auth_value, *dealer_pub_point);
if (!point_dh) {
LOG(ERROR) << "Failed to perform scalar multiplication of dealer_pub_point "
"and destination_share";
return false;
}
crypto::ScopedEC_POINT point_dest =
ec_.Add(*point_dh, *mediator_dh, context.get());
if (!point_dest) {
LOG(ERROR)
<< "Failed to perform point addition of point_dh and mediator_dh";
return false;
}
// Get point's affine X coordinate.
crypto::ScopedBIGNUM destination_dh_x = CreateBigNum();
if (!destination_dh_x) {
LOG(ERROR) << "Failed to allocate BIGNUM";
return false;
}
if (!ec_.GetAffineCoordinates(*point_dest, context.get(),
destination_dh_x.get(), /*y=*/nullptr)) {
LOG(ERROR) << "Failed to get point_dest x coordinate";
return false;
}
brillo::SecureBlob hkdf_secret;
// Convert X coordinate to fixed-size blob.
if (!BigNumToSecureBlob(*destination_dh_x, ec_.AffineCoordinateSizeInBytes(),
&hkdf_secret)) {
LOG(ERROR) << "Failed to convert destination_dh_x BIGNUM to SecureBlob";
return false;
}
if (!ComputeHkdfWithInfoSuffix(hkdf_secret, GetRecoveryKeyHkdfInfo(),
dealer_pub_key, /*salt=*/brillo::SecureBlob(),
HkdfHash::kSha256, /*result_len=*/0,
destination_recovery_key)) {
LOG(ERROR) << "Failed to compute HKDF of destination_dh";
return false;
}
return true;
}
bool RecoveryCryptoImpl::DecryptResponsePayload(
const brillo::SecureBlob& encrypted_channel_priv_key,
const CryptoRecoveryEpochResponse& epoch_response,
const CryptoRecoveryRpcResponse& recovery_response_proto,
HsmResponsePlainText* response_plain_text) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
RecoveryResponse recovery_response;
if (!GetRecoveryResponseFromProto(recovery_response_proto,
&recovery_response)) {
LOG(ERROR) << "Unable to deserialize Recovery Response from CBOR";
return false;
}
HsmResponseAssociatedData response_ad;
if (!DeserializeHsmResponseAssociatedDataFromCbor(
recovery_response.response_payload.associated_data, &response_ad)) {
LOG(ERROR) << "Unable to deserialize Response payload associated data";
return false;
}
if (!epoch_response.has_epoch_pub_key()) {
LOG(ERROR) << "Epoch response doesn't have epoch public key";
return false;
}
brillo::SecureBlob epoch_pub_key(epoch_response.epoch_pub_key());
crypto::ScopedEC_POINT epoch_pub_point =
ec_.SecureBlobToPoint(epoch_pub_key, context.get());
if (!epoch_pub_point) {
LOG(ERROR) << "Failed to convert epoch_pub_key SecureBlob to EC_POINT";
return false;
}
// Performs scalar multiplication of epoch_pub_point and channel_priv_key.
// Here we don't use key_auth_value generated from GenerateKeyAuthValue()
// because key_auth_value is unavailable and will only be recovered from the
// decrypted response afterward
crypto::ScopedEC_POINT shared_secret_point =
tpm_backend_->GenerateDiffieHellmanSharedSecret(
ec_, encrypted_channel_priv_key, /*auth_value=*/std::nullopt,
*epoch_pub_point);
if (!shared_secret_point) {
LOG(ERROR) << "Failed to compute shared point from epoch_pub_point and "
"channel_priv_key";
return false;
}
brillo::SecureBlob shared_secret_point_blob;
if (!ec_.PointToSecureBlob(*shared_secret_point, &shared_secret_point_blob,
context.get())) {
LOG(ERROR) << "Failed to convert shared_secret_point to a SecureBlob";
return false;
}
brillo::SecureBlob aes_gcm_key;
if (!GenerateEcdhHkdfSymmetricKey(ec_, *shared_secret_point, epoch_pub_key,
GetResponsePayloadPlainTextHkdfInfo(),
response_ad.response_payload_salt,
RecoveryCrypto::kHkdfHash,
kAesGcm256KeySize, &aes_gcm_key)) {
LOG(ERROR) << "Failed to generate ECDH+HKDF recipient key for response "
"plain text decryption";
return false;
}
// Dispose shared_secret_point after used
shared_secret_point.reset();
shared_secret_point_blob.clear();
brillo::SecureBlob response_plain_text_cbor;
if (!AesGcmDecrypt(recovery_response.response_payload.cipher_text,
recovery_response.response_payload.associated_data,
recovery_response.response_payload.tag, aes_gcm_key,
recovery_response.response_payload.iv,
&response_plain_text_cbor)) {
LOG(ERROR) << "Failed to perform AES-GCM decryption of response plain text";
return false;
}
if (!DeserializeHsmResponsePlainTextFromCbor(response_plain_text_cbor,
response_plain_text)) {
LOG(ERROR) << "Failed to deserialize Response plain text";
return false;
}
return true;
}
bool RecoveryCryptoImpl::GenerateHsmAssociatedData(
const brillo::SecureBlob& channel_pub_key,
const brillo::SecureBlob& rsa_pub_key,
const crypto::ScopedEC_KEY& publisher_key_pair,
const OnboardingMetadata& onboarding_metadata,
brillo::SecureBlob* hsm_associated_data) const {
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
LOG(ERROR) << "Failed to allocate BN_CTX structure";
return false;
}
// Construct associated data for HSM payload: AD = CBOR({publisher_pub_key,
// channel_pub_key, rsa_pub_key, onboarding_metadata}).
const EC_POINT* publisher_pub_point =
EC_KEY_get0_public_key(publisher_key_pair.get());
if (!publisher_pub_point) {
LOG(ERROR) << "Failed to get publisher_pub_point";
return false;
}
brillo::SecureBlob publisher_pub_key;
if (!ec_.PointToSecureBlob(*publisher_pub_point, &publisher_pub_key,
context.get())) {
LOG(ERROR) << "Failed to convert publisher_pub_key to a SecureBlob";
return false;
}
HsmAssociatedData hsm_ad;
hsm_ad.publisher_pub_key = publisher_pub_key;
hsm_ad.channel_pub_key = channel_pub_key;
hsm_ad.rsa_public_key = rsa_pub_key;
hsm_ad.onboarding_meta_data = onboarding_metadata;
if (!SerializeHsmAssociatedDataToCbor(hsm_ad, hsm_associated_data)) {
LOG(ERROR) << "Failed to generate HSM associated data cbor";
return false;
}
return true;
}
} // namespace cryptorecovery
} // namespace cryptohome