Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wallet openssl ed25519 #26770

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Wallet openssl ed25519 #26770

wants to merge 5 commits into from

Conversation

supermassive
Copy link
Collaborator

@supermassive supermassive commented Nov 27, 2024

Resolves brave/brave-browser#42579
SecReview: https://github.com/brave/reviews/issues/1808

  • Drop dependency on ed25519-dalek-bip32 and some related crates.
  • Use ED25519_* functions from OpenSSL as a replacement.
  • Implement SLIP-10 child key derivation scheme.
  • To be done in a follow up PR: support Cardano derivation scheme(SLIP-23) based on this PR

Submitter Checklist:

  • I confirm that no security/privacy review is needed and no other type of reviews are needed, or that I have requested them
  • There is a ticket for my issue
  • Used Github auto-closing keywords in the PR description above
  • Wrote a good PR/commit description
  • Squashed any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
  • Added appropriate labels (QA/Yes or QA/No; release-notes/include or release-notes/exclude; OS/...) to the associated issue
  • Checked the PR locally:
    • npm run test -- brave_browser_tests, npm run test -- brave_unit_tests wiki
    • npm run presubmit wiki, npm run gn_check, npm run tslint
  • Ran git rebase master (if needed)

Reviewer Checklist:

  • A security review is not needed, or a link to one is included in the PR description
  • New files have MPL-2.0 license header
  • Adequate test coverage exists to prevent regressions
  • Major classes, functions and non-trivial code blocks are well-commented
  • Changes in component dependencies are properly reflected in gn
  • Code follows the style guide
  • Test plan is specified in PR before merging

After-merge Checklist:

Test Plan:

@github-actions github-actions bot added CI/run-audit-deps Check for known npm/cargo vulnerabilities (audit_deps) feature/web3/wallet feature/web3/wallet/core labels Nov 27, 2024
@supermassive supermassive force-pushed the wallet_openssl_ed25519 branch from 963dcb0 to 8f325e8 Compare November 27, 2024 08:02
@supermassive supermassive marked this pull request as ready for review November 28, 2024 09:02
@supermassive supermassive requested review from a team and bridiver as code owners November 28, 2024 09:03
@supermassive supermassive force-pushed the wallet_openssl_ed25519 branch from e7e8470 to 1cd3484 Compare December 2, 2024 08:39
@supermassive supermassive force-pushed the wallet_openssl_ed25519 branch from 1cd3484 to efc3048 Compare December 3, 2024 05:00
@supermassive supermassive force-pushed the wallet_openssl_ed25519 branch from efc3048 to 0d76610 Compare December 4, 2024 08:40
Copy link
Contributor

github-actions bot commented Dec 4, 2024

[puLL-Merge] - brave/brave-core@26770

Description

This PR updates the implementation of HDKeyEd25519 in the Brave Wallet to use a Rust-based implementation instead of the previous C++ implementation. The main changes involve replacing the direct use of OpenSSL functions with calls to Rust functions for key generation, derivation, and signing operations.

Changes

Changes

  1. components/brave_wallet/browser/BUILD.gn:

    • Moved "rust:rust_lib" from dependencies to public_deps
  2. components/brave_wallet/browser/internal/BUILD.gn:

    • Removed "hd_key_utils.cc" and "hd_key_utils.h"
    • Added "rust:rust_lib" to public_deps
  3. components/brave_wallet/browser/internal/hd_key.cc:

    • Replaced OpenSSL HMAC function with Rust implementation
    • Updated key derivation logic to use Rust functions
  4. components/brave_wallet/browser/internal/hd_key_ed25519.cc and .h:

    • Completely refactored to use Rust-based implementation
    • Replaced direct OpenSSL calls with Rust function calls
    • Updated constructor, methods, and member variables
  5. components/brave_wallet/browser/internal/hd_key_utils.cc and .h:

    • Removed these files as they are no longer needed
  6. components/brave_wallet/browser/solana_keyring.cc and .h:

    • Updated to work with the new HDKeyEd25519 implementation
  7. components/brave_wallet/common/hash_utils.cc and .h:

    • Removed HmacSha512 function as it's now handled in Rust
  8. components/brave_wallet/rust/BUILD.gn:

    • Added dependency on ed25519-dalek-bip32
  9. components/brave_wallet/rust/Cargo.toml:

    • Added ed25519-dalek-bip32 dependency
  10. components/brave_wallet/rust/lib.rs:

    • Added new Rust functions for Ed25519 key generation, derivation, and signing
sequenceDiagram
    participant C as C++ Code
    participant R as Rust Code
    participant ED as Ed25519DalekExtendedSecretKey

    C->>R: generate_ed25519_extended_secret_key_from_seed(seed)
    R->>ED: ExtendedSigningKey::from_seed(seed)
    ED-->>R: ExtendedSigningKey
    R-->>C: Ed25519DalekExtendedSecretKey

    C->>R: derive(path)
    R->>ED: derive(&d_path)
    ED-->>R: DerivedKey
    R-->>C: Ed25519DalekExtendedSecretKey

    C->>R: sign(msg)
    R->>ED: signing_key.try_sign(msg)
    ED-->>R: Signature
    R-->>C: Ed25519DalekSignature
Loading

Possible Issues

  • The transition from C++ to Rust implementation may introduce subtle differences in behavior that could affect existing functionality.
  • There might be performance implications of using Rust FFI instead of direct C++ implementations.

Security Hotspots

  1. Key derivation and signing operations in the new Rust implementation should be carefully reviewed to ensure they maintain or improve upon the security of the previous implementation.
  2. The handling of private key material in the new implementation should be audited to ensure proper security measures are in place.

Copy link
Member

@kdenhartog kdenhartog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments for you as I'm starting to review.

From a quick look at BoringSSL I can't see any reason that there would be a difference between it and dalek such that key recovery attacks would occur, but I want to dig in a bit more and will do that as you get a chance to go through these changes.


// Validate keypair by signing and verifying signature to ensure included
// private key matches public key.
bool ValidateKeypair(base::span<const uint8_t, kEd25519KeypairSize> key_pair) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a simpler way to do this by just checking if the privateKey point is on the curve.

Let me find an example of this that's more efficient than performing a sign/verify check if you want to keep a validation check in. FWIW, we don't actually need to do these checks for Ed25519 since all outputs will be valid private keys, but I think it's a good check to still do. I just want to do it more efficiently.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not to check if a private key is valid, but to check if pubkey(second half) matches private key(first half).
That key_pair is stored as is after import and we must be sure that base58 address generated from pubkey actually matches private key used for signing.
Pubkey generation from private key is not exposed now from OpenSSL but I'm adding that for cardano needs in a follow up PR

void ED25519_pubkey_from_scalar(uint8_t out_public_key[32],
                                const uint8_t scalar[32]) {

  uint8_t e[32];
  OPENSSL_memcpy(e, scalar, 32);
  e[0] &= 248;
  e[31] &= 127;
  e[31] |= 64;

  ge_p3 A;

  x25519_ge_scalarmult_base(&A, e);
  ge_p3_tobytes(out_public_key, &A);

  CONSTTIME_DECLASSIFY(out_public_key, 32);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh ok, this makes more sense what you're trying to achieve here. I'll see if I can find a simpler way to achieve the goal you're after as well, but this achieves it as well.

return nullptr;
}
DCHECK(out_len == kSHA512Length);
auto hmac = HmacSha512(base::byte_span_from_cstring(kMasterSecret), seed);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be typed as a SecureVector still?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why it was a secure vector before. seed and mnemonic are not 'secure' in this way but have same or higher level of importance.

It makes sense to clear private keys from memory when wallet is locked after browser being idle for some time. But I don't see a consistent approach when/what we should clear at active runtime.

Copy link
Member

@kdenhartog kdenhartog Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is when it was introduced.

It seems like we may have just missed some of these as refactors occurred since the change. Let's keep this as a SecureVector in here and then can do a follow-up issue to be more consistent about using SecureVector once ZCash work is finished up. In general, my preference is that if it's a secret we don't want left in memory after the wallet is locked then we should set it as a SecureVector. The one area where this might be a bit tricky is going to be zcash with their various key structures hence wanting to wait until that's finished.

std::unique_ptr<HDKeyEd25519> HDKeyEd25519::GenerateFromSeedAndPath(
base::span<const uint8_t> seed,
std::string_view hd_path) {
auto hd_key = base::WrapUnique(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a SecureVector type too?

return false;
}
return true;
CHECK(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a browser crash here if this doesn't sign properly? I don't see a reason that this would occur, but I'm thinking it would be safer to not crash completely if say the message isn't as expected or something like that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, will fix

if (path_ != kMasterNode) {
VLOG(0) << "must derive only from master key";
// static
std::unique_ptr<HDKeyEd25519> HDKeyEd25519::GenerateFromSeedAndPath(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo for myself: check to make sure that BoringSSL behaves as expected with this compared to Dalek-Ed25519 to avoid key recovery attacks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI/run-audit-deps Check for known npm/cargo vulnerabilities (audit_deps) feature/web3/wallet/core feature/web3/wallet puLL-Merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use OpenSSL ed25519 cryptography instead of rust provided one.
2 participants