| // 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/crypto/recovery_crypto.h" |
| |
| #include <algorithm> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/logging.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/recovery_crypto_hsm_cbor_serialization.h" |
| |
| namespace cryptohome { |
| |
| namespace { |
| |
| brillo::SecureBlob GetRecoveryKeyHkdfInfo() { |
| return brillo::SecureBlob("recovery_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); |
| } |
| |
| } // namespace |
| |
| const char RecoveryCrypto::kMediatorShareHkdfInfoValue[] = |
| "hsm:publisher hsmplaintext"; |
| |
| const char RecoveryCrypto::kRequestPayloadPlainTextHkdfInfoValue[] = |
| "requestplaintext"; |
| |
| const char RecoveryCrypto::kResponsePayloadPlainTextHkdfInfoValue[] = |
| "responseplaintext"; |
| |
| const EllipticCurve::CurveType RecoveryCrypto::kCurve = |
| EllipticCurve::CurveType::kPrime256; |
| |
| const HkdfHash RecoveryCrypto::kHkdfHash = HkdfHash::kSha256; |
| |
| // Size of public/private key for EllipticCurve::CurveType::kPrime256. |
| constexpr size_t kEc256PubKeySize = 65; |
| constexpr size_t kEc256PrivKeySize = 32; |
| |
| namespace { |
| |
| // Cryptographic operations for cryptohome recovery performed on CPU (software |
| // emulation). |
| class RecoveryCryptoImpl : public RecoveryCrypto { |
| public: |
| explicit RecoveryCryptoImpl(EllipticCurve ec); |
| |
| ~RecoveryCryptoImpl() override; |
| |
| bool GenerateRequestPayload( |
| const HsmPayload& hsm_payload, |
| const brillo::SecureBlob& request_meta_data, |
| const brillo::SecureBlob& channel_priv_key, |
| const brillo::SecureBlob& channel_pub_key, |
| const brillo::SecureBlob& epoch_pub_key, |
| RequestPayload* request_payload, |
| brillo::SecureBlob* ephemeral_pub_key) const override; |
| bool GenerateHsmPayload(const brillo::SecureBlob& mediator_pub_key, |
| const brillo::SecureBlob& rsa_pub_key, |
| const brillo::SecureBlob& onboarding_metadata, |
| HsmPayload* hsm_payload, |
| brillo::SecureBlob* destination_share, |
| brillo::SecureBlob* recovery_key, |
| brillo::SecureBlob* channel_pub_key, |
| brillo::SecureBlob* channel_priv_key) const override; |
| bool GenerateShares(const brillo::SecureBlob& mediator_pub_key, |
| EncryptedMediatorShare* encrypted_mediator_share, |
| brillo::SecureBlob* destination_share, |
| brillo::SecureBlob* dealer_pub_key) const override; |
| bool GeneratePublisherKeys(const brillo::SecureBlob& dealer_pub_key, |
| brillo::SecureBlob* publisher_pub_key, |
| brillo::SecureBlob* publisher_dh) const override; |
| bool RecoverDestination( |
| const brillo::SecureBlob& publisher_pub_key, |
| const brillo::SecureBlob& destination_share, |
| const base::Optional<brillo::SecureBlob>& ephemeral_pub_key, |
| const brillo::SecureBlob& mediated_publisher_pub_key, |
| brillo::SecureBlob* destination_dh) const override; |
| bool DecryptResponsePayload( |
| const brillo::SecureBlob& channel_priv_key, |
| const brillo::SecureBlob& epoch_pub_key, |
| const brillo::SecureBlob& response_payload_ct, |
| const brillo::SecureBlob& response_payload_ad, |
| const brillo::SecureBlob& response_payload_iv, |
| const brillo::SecureBlob& response_payload_tag, |
| brillo::SecureBlob* response_plain_text) const override; |
| |
| private: |
| // Encrypts mediator share and stores as `encrypted_ms` with |
| // embedded ephemeral public key, AES-GCM tag and iv. Returns false if error |
| // occurred. |
| bool EncryptMediatorShare(const brillo::SecureBlob& mediator_pub_key, |
| const brillo::SecureBlob& mediator_share, |
| EncryptedMediatorShare* encrypted_ms, |
| BN_CTX* context) const; |
| bool GenerateRecoveryKey(const crypto::ScopedEC_POINT& recovery_pub_point, |
| const crypto::ScopedEC_KEY& dealer_key_pair, |
| brillo::SecureBlob* recovery_key) const; |
| // Generate ephemeral public and inverse public keys {G*x, G*-x} |
| bool GenerateEphemeralKey(brillo::SecureBlob* ephemeral_pub_key, |
| brillo::SecureBlob* ephemeral_inv_pub_key) const; |
| bool GenerateHsmAssociatedData(const brillo::SecureBlob& channel_pub_key, |
| const brillo::SecureBlob& rsa_pub_key, |
| const brillo::SecureBlob& onboarding_metadata, |
| brillo::SecureBlob* hsm_associated_data, |
| brillo::SecureBlob* publisher_priv_key, |
| brillo::SecureBlob* publisher_pub_key) const; |
| |
| EllipticCurve ec_; |
| }; |
| |
| RecoveryCryptoImpl::RecoveryCryptoImpl(EllipticCurve ec) : ec_(std::move(ec)) {} |
| |
| RecoveryCryptoImpl::~RecoveryCryptoImpl() = default; |
| |
| bool RecoveryCryptoImpl::EncryptMediatorShare( |
| const brillo::SecureBlob& mediator_pub_key, |
| const brillo::SecureBlob& mediator_share, |
| RecoveryCrypto::EncryptedMediatorShare* encrypted_ms, |
| BN_CTX* context) const { |
| brillo::SecureBlob ephemeral_priv_key; |
| if (!ec_.GenerateKeysAsSecureBlobs(&encrypted_ms->ephemeral_pub_key, |
| &ephemeral_priv_key, context)) { |
| LOG(ERROR) << "Failed to generate EC keys"; |
| 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 (!GenerateEcdhHkdfSenderKey(ec_, mediator_pub_key, |
| encrypted_ms->ephemeral_pub_key, |
| ephemeral_priv_key, GetMediatorShareHkdfInfo(), |
| /*hkdf_salt=*/brillo::SecureBlob(), kHkdfHash, |
| kAesGcm256KeySize, &aes_gcm_key)) { |
| LOG(ERROR) << "Failed to generate ECDH+HKDF sender keys"; |
| return false; |
| } |
| |
| // Dispose private key. |
| ephemeral_priv_key.clear(); |
| |
| if (!AesGcmEncrypt(mediator_share, /*ad=*/base::nullopt, aes_gcm_key, |
| &encrypted_ms->iv, &encrypted_ms->tag, |
| &encrypted_ms->encrypted_data)) { |
| LOG(ERROR) << "Failed to perform AES-GCM encryption"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| 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()); |
| 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"; |
| return false; |
| } |
| brillo::SecureBlob recovery_dh; |
| if (!ec_.PointToSecureBlob(*point_dh, &recovery_dh, context.get())) { |
| LOG(ERROR) << "Failed to convert EC_POINT to SecureBlob"; |
| return false; |
| } |
| // |salt| can be empty here because the input already has a high entropy. |
| return Hkdf(HkdfHash::kSha256, recovery_dh, GetRecoveryKeyHkdfInfo(), |
| /*salt=*/brillo::SecureBlob(), /*result_len=*/0, recovery_key); |
| } |
| |
| 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::GenerateRequestPayload( |
| const HsmPayload& hsm_payload, |
| const brillo::SecureBlob& request_meta_data, |
| const brillo::SecureBlob& channel_priv_key, |
| const brillo::SecureBlob& channel_pub_key, |
| const brillo::SecureBlob& epoch_pub_key, |
| RequestPayload* request_payload, |
| brillo::SecureBlob* ephemeral_pub_key) const { |
| if (!SerializeRecoveryRequestAssociatedDataToCbor( |
| hsm_payload.cipher_text, hsm_payload.associated_data, hsm_payload.iv, |
| hsm_payload.tag, request_meta_data, epoch_pub_key, |
| &request_payload->associated_data)) { |
| LOG(ERROR) << "Failed to generate associated data cbor"; |
| 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 (!GenerateEcdhHkdfSenderKey(ec_, epoch_pub_key, channel_pub_key, |
| channel_priv_key, |
| GetRequestPayloadPlainTextHkdfInfo(), |
| /*hkdf_salt=*/brillo::SecureBlob(), kHkdfHash, |
| kAesGcm256KeySize, &aes_gcm_key)) { |
| LOG(ERROR) << "Failed to generate ECDH+HKDF sender keys"; |
| return false; |
| } |
| |
| 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; |
| if (!SerializeRecoveryRequestPlainTextToCbor(ephemeral_inverse_pub_key, |
| &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"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool RecoveryCryptoImpl::GenerateHsmPayload( |
| const brillo::SecureBlob& mediator_pub_key, |
| const brillo::SecureBlob& rsa_pub_key, |
| const brillo::SecureBlob& onboarding_metadata, |
| HsmPayload* hsm_payload, |
| brillo::SecureBlob* destination_share, |
| brillo::SecureBlob* recovery_key, |
| brillo::SecureBlob* channel_pub_key, |
| brillo::SecureBlob* channel_priv_key) const { |
| ScopedBN_CTX context = CreateBigNumContext(); |
| if (!context.get()) { |
| LOG(ERROR) << "Failed to allocate BN_CTX structure"; |
| 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; |
| } |
| // Generate two shares and a secret equal to the sum. |
| // Loop until the sum of two shares is non-zero (modulo order). |
| crypto::ScopedBIGNUM secret; |
| crypto::ScopedBIGNUM destination_share_bn = |
| ec_.RandomNonZeroScalar(context.get()); |
| if (!destination_share_bn) { |
| LOG(ERROR) << "Failed to generate secret"; |
| return false; |
| } |
| crypto::ScopedBIGNUM mediator_share_bn; |
| do { |
| mediator_share_bn = ec_.RandomNonZeroScalar(context.get()); |
| if (!mediator_share_bn) { |
| LOG(ERROR) << "Failed to generate secret"; |
| return false; |
| } |
| secret = |
| ec_.ModAdd(*mediator_share_bn, *destination_share_bn, context.get()); |
| if (!secret) { |
| LOG(ERROR) << "Failed to perform modulo addition"; |
| return false; |
| } |
| } while (BN_is_zero(secret.get())); |
| |
| if (!BigNumToSecureBlob(*destination_share_bn, ec_.ScalarSizeInBytes(), |
| destination_share)) { |
| LOG(ERROR) << "Failed to convert BIGNUM to SecureBlob"; |
| return false; |
| } |
| crypto::ScopedEC_POINT recovery_pub_point = |
| ec_.MultiplyWithGenerator(*secret, context.get()); |
| if (!recovery_pub_point) { |
| LOG(ERROR) << "Failed to perform MultiplyWithGenerator operation"; |
| return false; |
| } |
| |
| // Generate channel key pair. |
| // TODO(b/194678588): channel private key should be protected via TPM. |
| 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 (!ec_.PointToSecureBlob(*channel_pub_point, channel_pub_key, |
| context.get())) { |
| LOG(ERROR) << "Failed to convert channel_pub_key to a SecureBlob"; |
| return false; |
| } |
| const BIGNUM* channel_priv_key_bn = |
| EC_KEY_get0_private_key(channel_key_pair.get()); |
| if (!BigNumToSecureBlob(*channel_priv_key_bn, ec_.ScalarSizeInBytes(), |
| channel_priv_key)) { |
| LOG(ERROR) << "Failed to convert channel_priv_key_bn to a SecureBlob"; |
| return false; |
| } |
| |
| // Construct associated data for HSM payload: AD = CBOR({publisher_pub_key, |
| // channel_pub_key, rsa_pub_key, onboarding_metadata}). |
| brillo::SecureBlob publisher_priv_key; |
| brillo::SecureBlob publisher_pub_key; |
| if (!GenerateHsmAssociatedData(*channel_pub_key, rsa_pub_key, |
| onboarding_metadata, |
| &hsm_payload->associated_data, |
| &publisher_priv_key, &publisher_pub_key)) { |
| LOG(ERROR) << "Failed to generate associated data cbor"; |
| 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()); |
| 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; |
| } |
| // TODO(mslus): in the initial version kav will be empty (as it should for |
| // TPM 2.0). In the next iteration we will generate kav if a non-empty value |
| // of `rsa_pub_key` is provided. |
| brillo::SecureBlob plain_text_cbor; |
| if (!SerializeHsmPlainTextToCbor(mediator_share, dealer_pub_key, |
| /*key_auth_value=*/brillo::SecureBlob(), |
| &plain_text_cbor)) { |
| LOG(ERROR) << "Failed to generate HSM plain text cbor"; |
| 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 (!GenerateEcdhHkdfSenderKey(ec_, mediator_pub_key, publisher_pub_key, |
| publisher_priv_key, GetMediatorShareHkdfInfo(), |
| /*hkdf_salt=*/brillo::SecureBlob(), kHkdfHash, |
| kAesGcm256KeySize, &aes_gcm_key)) { |
| LOG(ERROR) << "Failed to generate ECDH+HKDF sender keys"; |
| 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"; |
| return false; |
| } |
| |
| // Cleanup: all intermediate secrets must be securely disposed at the end of |
| // HSM payload generation. |
| aes_gcm_key.clear(); |
| plain_text_cbor.clear(); |
| mediator_share.clear(); |
| dealer_pub_key.clear(); |
| publisher_pub_key.clear(); |
| publisher_priv_key.clear(); |
| |
| GenerateRecoveryKey(recovery_pub_point, dealer_key_pair, recovery_key); |
| return true; |
| } |
| |
| bool RecoveryCryptoImpl::GenerateShares( |
| const brillo::SecureBlob& mediator_pub_key, |
| EncryptedMediatorShare* encrypted_mediator_share, |
| brillo::SecureBlob* destination_share, |
| brillo::SecureBlob* dealer_pub_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::ScopedBIGNUM secret; |
| crypto::ScopedBIGNUM destination_share_bn = |
| ec_.RandomNonZeroScalar(context.get()); |
| if (!destination_share_bn) { |
| LOG(ERROR) << "Failed to generate secret"; |
| return false; |
| } |
| crypto::ScopedBIGNUM mediator_share_bn; |
| do { |
| mediator_share_bn = ec_.RandomNonZeroScalar(context.get()); |
| if (!mediator_share_bn) { |
| LOG(ERROR) << "Failed to generate secret"; |
| return false; |
| } |
| secret = |
| ec_.ModAdd(*mediator_share_bn, *destination_share_bn, context.get()); |
| if (!secret) { |
| LOG(ERROR) << "Failed to perform modulo addition"; |
| return false; |
| } |
| } while (BN_is_zero(secret.get())); |
| |
| crypto::ScopedEC_POINT dealer_pub_point = |
| ec_.MultiplyWithGenerator(*secret, context.get()); |
| if (!dealer_pub_point) { |
| LOG(ERROR) << "Failed to perform MultiplyWithGenerator operation"; |
| return false; |
| } |
| brillo::SecureBlob mediator_share; |
| if (!BigNumToSecureBlob(*mediator_share_bn, ec_.ScalarSizeInBytes(), |
| &mediator_share)) { |
| LOG(ERROR) << "Failed to convert BIGNUM to SecureBlob"; |
| return false; |
| } |
| if (!BigNumToSecureBlob(*destination_share_bn, ec_.ScalarSizeInBytes(), |
| destination_share)) { |
| LOG(ERROR) << "Failed to convert BIGNUM to SecureBlob"; |
| return false; |
| } |
| if (!ec_.PointToSecureBlob(*dealer_pub_point, dealer_pub_key, |
| context.get())) { |
| LOG(ERROR) << "Failed to convert EC_POINT to SecureBlob"; |
| return false; |
| } |
| if (!EncryptMediatorShare(mediator_pub_key, mediator_share, |
| encrypted_mediator_share, context.get())) { |
| LOG(ERROR) << "Failed to encrypt mediator share"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool RecoveryCryptoImpl::GeneratePublisherKeys( |
| const brillo::SecureBlob& dealer_pub_key, |
| brillo::SecureBlob* publisher_pub_key, |
| brillo::SecureBlob* publisher_recovery_key) const { |
| ScopedBN_CTX context = CreateBigNumContext(); |
| if (!context.get()) { |
| LOG(ERROR) << "Failed to allocate BN_CTX structure"; |
| return false; |
| } |
| crypto::ScopedBIGNUM secret = ec_.RandomNonZeroScalar(context.get()); |
| if (!secret) { |
| LOG(ERROR) << "Failed to generate secret"; |
| return false; |
| } |
| crypto::ScopedEC_POINT publisher_pub_point = |
| ec_.MultiplyWithGenerator(*secret, context.get()); |
| if (!publisher_pub_point) { |
| LOG(ERROR) << "Failed to perform MultiplyWithGenerator operation"; |
| return false; |
| } |
| crypto::ScopedEC_POINT dealer_pub_point = |
| ec_.SecureBlobToPoint(dealer_pub_key, context.get()); |
| if (!dealer_pub_point) { |
| LOG(ERROR) << "Failed to convert SecureBlob to EC_point"; |
| return false; |
| } |
| crypto::ScopedEC_POINT point_dh = |
| ec_.Multiply(*dealer_pub_point, *secret, context.get()); |
| if (!point_dh) { |
| LOG(ERROR) << "Failed to perform point multiplication"; |
| return false; |
| } |
| if (!ec_.PointToSecureBlob(*publisher_pub_point, publisher_pub_key, |
| context.get())) { |
| LOG(ERROR) << "Failed to convert EC_POINT to SecureBlob"; |
| return false; |
| } |
| brillo::SecureBlob publisher_dh; |
| if (!ec_.PointToSecureBlob(*point_dh, &publisher_dh, context.get())) { |
| LOG(ERROR) << "Failed to convert EC_POINT to SecureBlob"; |
| return false; |
| } |
| // |hkdf_salt| can be empty here because the input already has a high entropy. |
| if (!Hkdf(HkdfHash::kSha256, publisher_dh, GetRecoveryKeyHkdfInfo(), |
| /*salt=*/brillo::SecureBlob(), |
| /*result_len=*/0, publisher_recovery_key)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool RecoveryCryptoImpl::RecoverDestination( |
| const brillo::SecureBlob& publisher_pub_key, |
| const brillo::SecureBlob& destination_share, |
| const base::Optional<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::ScopedBIGNUM destination_share_bn = |
| SecureBlobToBigNum(destination_share); |
| if (!destination_share_bn) { |
| LOG(ERROR) << "Failed to convert SecureBlob to BIGNUM"; |
| return false; |
| } |
| crypto::ScopedEC_POINT publisher_pub_point = |
| ec_.SecureBlobToPoint(publisher_pub_key, context.get()); |
| if (!publisher_pub_point) { |
| LOG(ERROR) << "Failed to convert SecureBlob to EC_POINT"; |
| return false; |
| } |
| crypto::ScopedEC_POINT mediator_dh = |
| ec_.SecureBlobToPoint(mediated_publisher_pub_key, context.get()); |
| if (!mediator_dh) { |
| LOG(ERROR) << "Failed to convert SecureBlob to EC_POINT"; |
| return false; |
| } |
| // TODO(b/194884283): Make ephemeral_pub_key non-optional after old protocol |
| // version is removed. |
| if (ephemeral_pub_key.has_value()) { |
| // Performs addition of mediator_dh and ephemeral_pub_key. |
| crypto::ScopedEC_POINT ephemeral_pub_point = |
| ec_.SecureBlobToPoint(ephemeral_pub_key.value(), context.get()); |
| if (!ephemeral_pub_point) { |
| LOG(ERROR) << "Failed to convert SecureBlob to EC_POINT"; |
| return false; |
| } |
| mediator_dh = ec_.Add(*mediator_dh, *ephemeral_pub_point, context.get()); |
| if (!mediator_dh) { |
| LOG(ERROR) << "Failed to add mediator_dh and ephemeral_pub_point"; |
| return false; |
| } |
| } |
| // Performs scalar multiplication of publisher_pub_key and destination_share. |
| crypto::ScopedEC_POINT point_dh = |
| ec_.Multiply(*publisher_pub_point, *destination_share_bn, context.get()); |
| if (!point_dh) { |
| LOG(ERROR) << "Failed to perform scalar multiplication"; |
| 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"; |
| return false; |
| } |
| brillo::SecureBlob destination_dh; |
| if (!ec_.PointToSecureBlob(*point_dest, &destination_dh, context.get())) { |
| LOG(ERROR) << "Failed to convert EC_POINT to SecureBlob"; |
| return false; |
| } |
| // |hkdf_salt| can be empty here because the input already has a high entropy. |
| if (!Hkdf(HkdfHash::kSha256, destination_dh, GetRecoveryKeyHkdfInfo(), |
| /*salt=*/brillo::SecureBlob(), /*result_len=*/0, |
| destination_recovery_key)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool RecoveryCryptoImpl::DecryptResponsePayload( |
| const brillo::SecureBlob& channel_priv_key, |
| const brillo::SecureBlob& epoch_pub_key, |
| const brillo::SecureBlob& response_payload_ct, |
| const brillo::SecureBlob& response_payload_ad, |
| const brillo::SecureBlob& response_payload_iv, |
| const brillo::SecureBlob& response_payload_tag, |
| brillo::SecureBlob* response_plain_text) const { |
| brillo::SecureBlob aes_gcm_key; |
| if (!GenerateEcdhHkdfRecipientKey(ec_, channel_priv_key, epoch_pub_key, |
| GetResponsePayloadPlainTextHkdfInfo(), |
| /*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(response_payload_ct, response_payload_ad, |
| response_payload_tag, aes_gcm_key, response_payload_iv, |
| response_plain_text)) { |
| LOG(ERROR) << "Failed to perform AES-GCM decryption"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool RecoveryCryptoImpl::GenerateHsmAssociatedData( |
| const brillo::SecureBlob& channel_pub_key, |
| const brillo::SecureBlob& rsa_pub_key, |
| const brillo::SecureBlob& onboarding_metadata, |
| brillo::SecureBlob* hsm_associated_data, |
| brillo::SecureBlob* publisher_priv_key, |
| brillo::SecureBlob* publisher_pub_key) const { |
| ScopedBN_CTX context = CreateBigNumContext(); |
| if (!context.get()) { |
| LOG(ERROR) << "Failed to allocate BN_CTX structure"; |
| return false; |
| } |
| |
| // Generate publisher key pair. |
| 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}). |
| const EC_POINT* publisher_pub_point = |
| EC_KEY_get0_public_key(publisher_key_pair.get()); |
| if (!ec_.PointToSecureBlob(*publisher_pub_point, publisher_pub_key, |
| context.get())) { |
| LOG(ERROR) << "Failed to convert publisher_pub_key to a SecureBlob"; |
| return false; |
| } |
| const BIGNUM* publisher_priv_secret = |
| EC_KEY_get0_private_key(publisher_key_pair.get()); |
| if (!BigNumToSecureBlob(*publisher_priv_secret, ec_.ScalarSizeInBytes(), |
| publisher_priv_key)) { |
| LOG(ERROR) << "Failed to convert publisher_priv_key to a SecureBlob"; |
| return false; |
| } |
| if (!SerializeHsmAssociatedDataToCbor(*publisher_pub_key, channel_pub_key, |
| rsa_pub_key, onboarding_metadata, |
| hsm_associated_data)) { |
| LOG(ERROR) << "Failed to generate associated data cbor"; |
| return false; |
| } |
| return true; |
| } |
| |
| // Appends `src_blob` to `dst_blob`. |
| void AppendToSecureBlob(const brillo::SecureBlob& src_blob, |
| brillo::SecureBlob* dst_blob) { |
| dst_blob->insert(dst_blob->end(), src_blob.begin(), src_blob.end()); |
| } |
| |
| // Copies SecureBlob chunk of given size `chunk_size` starting at iterator `it` |
| // to `dst_blob`. Returns iterator pointing to first byte after the copied |
| // chunk. |
| brillo::SecureBlob::const_iterator CopySecureBlobChunk( |
| const brillo::SecureBlob::const_iterator& it, |
| size_t chunk_size, |
| brillo::SecureBlob* dst_blob) { |
| dst_blob->assign(it, it + chunk_size); |
| return it + chunk_size; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<RecoveryCrypto> RecoveryCrypto::Create() { |
| ScopedBN_CTX context = CreateBigNumContext(); |
| if (!context.get()) { |
| LOG(ERROR) << "Failed to allocate BN_CTX structure"; |
| return nullptr; |
| } |
| base::Optional<EllipticCurve> ec = |
| EllipticCurve::Create(kCurve, context.get()); |
| if (!ec) { |
| LOG(ERROR) << "Failed to create EllipticCurve"; |
| return nullptr; |
| } |
| return std::make_unique<RecoveryCryptoImpl>(std::move(*ec)); |
| } |
| |
| RecoveryCrypto::~RecoveryCrypto() = default; |
| |
| bool RecoveryCrypto::SerializeEncryptedMediatorShareForTesting( |
| const EncryptedMediatorShare& encrypted_mediator_share, |
| brillo::SecureBlob* serialized_blob) { |
| if (encrypted_mediator_share.tag.size() != kAesGcmTagSize) { |
| LOG(ERROR) << "Invalid tag size in encrypted mediator share"; |
| return false; |
| } |
| if (encrypted_mediator_share.iv.size() != kAesGcmIVSize) { |
| LOG(ERROR) << "Invalid iv size in encrypted mediator share"; |
| return false; |
| } |
| if (encrypted_mediator_share.ephemeral_pub_key.size() != kEc256PubKeySize) { |
| LOG(ERROR) |
| << "Invalid ephemeral public key size in encrypted mediator share"; |
| return false; |
| } |
| if (encrypted_mediator_share.encrypted_data.size() != kEc256PrivKeySize) { |
| LOG(ERROR) |
| << "Invalid ephemeral public key size in encrypted mediator share"; |
| return false; |
| } |
| serialized_blob->clear(); |
| serialized_blob->reserve(kAesGcmTagSize + kAesGcmIVSize + kEc256PubKeySize + |
| kEc256PrivKeySize); |
| AppendToSecureBlob(encrypted_mediator_share.tag, serialized_blob); |
| AppendToSecureBlob(encrypted_mediator_share.iv, serialized_blob); |
| AppendToSecureBlob(encrypted_mediator_share.ephemeral_pub_key, |
| serialized_blob); |
| AppendToSecureBlob(encrypted_mediator_share.encrypted_data, serialized_blob); |
| return true; |
| } |
| |
| bool RecoveryCrypto::DeserializeEncryptedMediatorShareForTesting( |
| const brillo::SecureBlob& serialized_blob, |
| EncryptedMediatorShare* encrypted_mediator_share) { |
| if (serialized_blob.size() != |
| kAesGcmTagSize + kAesGcmIVSize + kEc256PubKeySize + kEc256PrivKeySize) { |
| LOG(ERROR) << "Invalid size of serialized encrypted mediator share"; |
| return false; |
| } |
| auto it = serialized_blob.begin(); |
| it = CopySecureBlobChunk(it, kAesGcmTagSize, &encrypted_mediator_share->tag); |
| it = CopySecureBlobChunk(it, kAesGcmIVSize, &encrypted_mediator_share->iv); |
| it = CopySecureBlobChunk(it, kEc256PubKeySize, |
| &encrypted_mediator_share->ephemeral_pub_key); |
| it = CopySecureBlobChunk(it, kEc256PrivKeySize, |
| &encrypted_mediator_share->encrypted_data); |
| DCHECK(it == serialized_blob.end()); |
| return true; |
| } |
| |
| } // namespace cryptohome |