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

[Crypto] KeyGen improvement #3788

Merged
merged 19 commits into from
Feb 10, 2023
Merged

[Crypto] KeyGen improvement #3788

merged 19 commits into from
Feb 10, 2023

Conversation

tarakby
Copy link
Contributor

@tarakby tarakby commented Jan 8, 2023

So far, key generation implementation assumes the input seed has uniformly distributed entropy. This is only valid if function callers use the output of a secure RNG as a seed.
This PR relaxes the strong seed assumption and implements further mechanism to extract the input entropy and use it in the rest of the process. A seed with enough entropy is now sufficient to generate keys.

  • update BLS key generation seed handling by implementing the IETF draft algorithm in section 2.3.
    • this improves compatibility to IETF draft recommendations.
    • improves handling "bad" seeds with non uniform entropy.
    • a test against BLST key gen is added for compatibility check.
  • update ECDSA key generation by using HKDF to extract entropy from input seed and expand it into key bytes.
    • improves handling "bad" seeds with non uniform entropy.
  • minor improvement by overwriting sensitive data in memory after computation.
  • consolidate minimum seed length as a module constant instead of an algorithm specific length (consequence of the algorithm update)

Side change:

  • add SHA2-256 light computation function (outside of the existing interface).

@codecov-commenter
Copy link

codecov-commenter commented Jan 8, 2023

Codecov Report

Merging #3788 (6ab5322) into master (4618f93) will increase coverage by 53.33%.
The diff coverage is n/a.

@@             Coverage Diff             @@
##           master    #3788       +/-   ##
===========================================
+ Coverage        0   53.33%   +53.33%     
===========================================
  Files           0      819      +819     
  Lines           0    76757    +76757     
===========================================
+ Hits            0    40935    +40935     
- Misses          0    32511    +32511     
- Partials        0     3311     +3311     
Flag Coverage Δ
unittests 53.33% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
state/protocol/seed/customizers.go 57.14% <ø> (ø)
module/metrics/collection.go 0.00% <0.00%> (ø)
...ensus/hotstuff/signature/randombeacon_inspector.go 78.43% <0.00%> (ø)
state/cluster/badger/params.go 100.00% <0.00%> (ø)
ledger/common/hash/sha3.go 100.00% <0.00%> (ø)
engine/protocol/handler.go 0.00% <0.00%> (ø)
engine/access/ping/node_info.go 12.12% <0.00%> (ø)
ledger/partial/ledger.go 56.70% <0.00%> (ø)
model/flow/resultApproval.go 11.11% <0.00%> (ø)
module/metrics/verification.go 0.00% <0.00%> (ø)
... and 810 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

Comment on lines +29 to +38
func overwrite(data []byte) {
_, err := rand.Read(data) // checking err is enough
if err != nil {
// zero the buffer if randomizing failed
for i := 0; i < len(data); i++ {
data[i] = 0
}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

is there any benefit in overwriting with random data instead of just writing zeros in the first place?

Suggested change
func overwrite(data []byte) {
_, err := rand.Read(data) // checking err is enough
if err != nil {
// zero the buffer if randomizing failed
for i := 0; i < len(data); i++ {
data[i] = 0
}
}
}
func overwrite(data []byte) {
for i := range data {
data[i] = 0
}
}

BoringSSL simply memsets the memory on free:

void OPENSSL_cleanse(void *ptr, size_t len) {
#if defined(OPENSSL_WINDOWS)
  SecureZeroMemory(ptr, len);
#else
  OPENSSL_memset(ptr, 0, len);

#if !defined(OPENSSL_NO_ASM)
  /* As best as we can tell, this is sufficient to break any optimisations that
     might try to eliminate "superfluous" memsets. If there's an easy way to
     detect memset_s, it would be better to use that. */
  __asm__ __volatile__("" : : "r"(ptr) : "memory");
#endif
#endif  // !OPENSSL_NO_ASM
}

(with an additional caveat of preventing compiler optimizations.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question. First, this mitigation (either randomizing or zeroing) can only be helpful against "static" memory dump attacks that happen after the computation has ended. However, a more powerful attacker could use "dynamic" memory dumps during the computation to read sensitive data. In that case, the final "static" dump can help identify the memory areas to look at. Zeroing the sensitive areas gives an obvious hint for such attacks.
Of course, an experimented attacker may avoid this mitigation by comparing multiple dynamic memory dumps and seeing the buffers evolution (especially that Golang initializes new allocated buffers with zeroes 😞 ). Randomizing instead of zeroing makes the attack slightly harder but not impossible. Randomizing is also a common practice in "grey box" cryptography implementations.

Another limitation of overwriting buffers after use (either by randomizing or zeroing) is that it assumes the sensitive buffer was not copied in other areas. Such copies could have been made to the stack or registers, and these need to be overwritten as well. Looking at the assembly level for a specific compiler/architecture can answer the question, but that's not possible in our case (lib is written without a specific target compiler/arch).
This mitigation is nice to have but I don't rely much on it, the library is still vulnerable to memory dumps and should only be run in safe environments.

Now back to the compiler optimizations: I am still wondering how to force the Go complier to not omit the call to overwrite(). Any thoughts about that?

Copy link
Contributor

Choose a reason for hiding this comment

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

We may be overthinking it too much. Our goal is to do a due diligence so that the memory we free won't have secret material in it. As long as we can check that memset is not optimized out by compiler, we should be fine. (otherwise we down the rabbithole of software memory enclaves, mlock, PROT_NONE, and other "fun" system things)

For the long term solution, a runtime/secret is probably the right way of solving it: golang/go#21865

PS. As far as physical access goes, I think this is an uphill battle: w/o enabled IOMMU one has full memory access via the PCIe DMA.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the long term solution, a runtime/secret is probably the right way of solving it

Nice, yes I can wait for that to be available in Go. Thanks!

As far as physical access goes, I think this is an uphill battle.

Once our model includes physical access, our library is totally insecure, even without memory access (because of side channel leakages). In my comment above, I was only considering remote memory access.

My overall opinion:

  • this mitigation makes the lib more secure only in a "weak" attack model. In other models it is not very relevant IMHO.
  • It doesn't cost much to add it, so I decided to add a slightly stronger mitigation, that is commonly used in grey box crypto implementations (overthinking there is required and common).
  • It is noticeable in security audits so it's good to add it for that purpose.

@tarakby tarakby self-assigned this Jan 9, 2023
@tarakby tarakby marked this pull request as ready for review January 10, 2023 02:33
@tarakby tarakby changed the title WIP [crypto] KeyGen update [Crypto] KeyGen improvement Jan 10, 2023
@AlexHentschel
Copy link
Member

misc additional question regarding

// sha2_256Algo, embeds commonHasher
type sha2_256Algo struct {
hash.Hash
}

I didn't find a struct commonHasher in the code base. Is this outdated? Or is it just a typo, and the comment should read "... embeds common hasher" ?

Copy link
Member

@AlexHentschel AlexHentschel left a comment

Choose a reason for hiding this comment

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

Looks good to me (but don't have the background to assess the correctness of the low-level crypto implementation)

crypto/bls.go Outdated Show resolved Hide resolved
crypto/bls.go Show resolved Hide resolved
crypto/bls12381_utils.c Outdated Show resolved Hide resolved
crypto/bls12381_utils.c Outdated Show resolved Hide resolved
crypto/bls12381_utils.go Outdated Show resolved Hide resolved
crypto/ecdsa.go Outdated Show resolved Hide resolved
crypto/hash/sha2.go Outdated Show resolved Hide resolved
crypto/sign.go Outdated Show resolved Hide resolved
crypto/sign.go Outdated Show resolved Hide resolved
crypto/sign_test_utils.go Show resolved Hide resolved
@tarakby
Copy link
Contributor Author

tarakby commented Feb 10, 2023

bors merge

bors bot added a commit that referenced this pull request Feb 10, 2023
3788: [Crypto] KeyGen improvement r=tarakby a=tarakby

So far, key generation implementation assumes the input seed has uniformly distributed entropy. This is only valid if function callers use the output of a secure RNG as a seed. 
This PR relaxes the strong seed assumption and implements further mechanism to extract the input entropy and use it in the rest of the process. A seed with enough entropy is now sufficient to generate keys.


- update BLS key generation seed handling by implementing the[ IETF draft algorithm](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#name-keygen) in section 2.3. 
   - this improves compatibility to IETF draft recommendations.
   - improves handling "bad" seeds with non uniform entropy.
   - a test against BLST key gen is added for compatibility check.
- update ECDSA key generation by using HKDF to extract entropy from input seed and expand it into key bytes. 
  - improves handling "bad" seeds with non uniform entropy.
- minor improvement by overwriting sensitive data in memory after computation.
- consolidate minimum seed length as a module constant instead of an algorithm specific length (consequence of the algorithm update) 
  
Side change:
 - add SHA2-256 light computation function (outside of the existing interface). 


3789: [Tools] Add bootstrap command to generate grpc TLS keys r=peterargue a=peterargue

This new command allows generating Access API TLS keys

Co-authored-by: Tarak Ben Youssef <tarak.benyoussef@dapperlabs.com>
Co-authored-by: Tarak Ben Youssef <50252200+tarakby@users.noreply.github.com>
Co-authored-by: Peter Argue <89119817+peterargue@users.noreply.github.com>
@bors
Copy link
Contributor

bors bot commented Feb 10, 2023

Build failed (retrying...):

bors bot added a commit that referenced this pull request Feb 10, 2023
3788: [Crypto] KeyGen improvement r=tarakby a=tarakby

So far, key generation implementation assumes the input seed has uniformly distributed entropy. This is only valid if function callers use the output of a secure RNG as a seed. 
This PR relaxes the strong seed assumption and implements further mechanism to extract the input entropy and use it in the rest of the process. A seed with enough entropy is now sufficient to generate keys.


- update BLS key generation seed handling by implementing the[ IETF draft algorithm](https://www.ietf.org/archive/id/draft-irtf-cfrg-bls-signature-05.html#name-keygen) in section 2.3. 
   - this improves compatibility to IETF draft recommendations.
   - improves handling "bad" seeds with non uniform entropy.
   - a test against BLST key gen is added for compatibility check.
- update ECDSA key generation by using HKDF to extract entropy from input seed and expand it into key bytes. 
  - improves handling "bad" seeds with non uniform entropy.
- minor improvement by overwriting sensitive data in memory after computation.
- consolidate minimum seed length as a module constant instead of an algorithm specific length (consequence of the algorithm update) 
  
Side change:
 - add SHA2-256 light computation function (outside of the existing interface). 


Co-authored-by: Tarak Ben Youssef <tarak.benyoussef@dapperlabs.com>
Co-authored-by: Tarak Ben Youssef <50252200+tarakby@users.noreply.github.com>
@bors
Copy link
Contributor

bors bot commented Feb 10, 2023

Build failed:

@tarakby tarakby merged commit 0eb3586 into master Feb 10, 2023
@tarakby tarakby deleted the tarak/6354-keygen-update branch February 10, 2023 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants