-
Notifications
You must be signed in to change notification settings - Fork 885
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
base: master
Are you sure you want to change the base?
Wallet openssl ed25519 #26770
Conversation
963dcb0
to
8f325e8
Compare
e7e8470
to
1cd3484
Compare
1cd3484
to
efc3048
Compare
efc3048
to
0d76610
Compare
[puLL-Merge] - brave/brave-core@26770 DescriptionThis 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. ChangesChanges
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
Possible Issues
Security Hotspots
|
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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);
}
There was a problem hiding this comment.
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.
third_party/rust/chromium_crates_io/vendor/ed25519-dalek-2.1.1/Cargo.toml
Show resolved
Hide resolved
return nullptr; | ||
} | ||
DCHECK(out_len == kSHA512Length); | ||
auto hmac = HmacSha512(base::byte_span_from_cstring(kMasterSecret), seed); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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
Resolves brave/brave-browser#42579
SecReview: https://github.com/brave/reviews/issues/1808
ed25519-dalek-bip32
and some related crates.ED25519_*
functions from OpenSSL as a replacement.Submitter Checklist:
QA/Yes
orQA/No
;release-notes/include
orrelease-notes/exclude
;OS/...
) to the associated issuenpm run test -- brave_browser_tests
,npm run test -- brave_unit_tests
wikinpm run presubmit
wiki,npm run gn_check
,npm run tslint
git rebase master
(if needed)Reviewer Checklist:
gn
After-merge Checklist:
changes has landed on
Test Plan: