Skip to content

Add feature zeroize #262

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

Closed
wants to merge 1 commit into from
Closed

Conversation

tcharding
Copy link
Member

@tcharding tcharding commented Dec 22, 2020

It is useful for downstream users to be able to zeroize secrets. Doing so is supported by the zeroize library.

Add feature zeroize which, if enabled, implements Zeroize for all our types that use impl_array_newtype (includes SecretKey and KeyPair).

On purpose we do not implement Drop and we still implement Copy. This is so as to not negatively effect users of the library (see link below from when this was attempted previously). This means that stack variables are not zerozied and there may be additional copies made by the compiler.

Adds a section to the README explaining the aims of the zeroize feature, included here:

Zeroizing secrets is supported using the `zeroize` feature. Enabling this
feature changes the MSRV to 1.44 (see https://docs.rs/zeroize/1.2.0/zeroize/).

The aim of the `zeroize` feature is to allow users of this library to reduce the
number of copies of secrets in memory (by wrapping our types, implementing
`Zeroize` and implementing `Drop` (i.e. derive `Zeroize`). Our aim is not to
guarantee that there are no copies left un-zeroed in memory. Internally we
absolutely do leave secrets on the stack.

> I think even zeroing some out the copies is better than none, because every
> copy makes a Heartbleed-like attack a little easier.

@elichai this quote is from you in this PR's thread, I gave not attribution of the source of the quote. Pleases say so if you would like attribution (or if you would prefer not to be quoted).

Please note, PR introduces a new dependency on zeroize v1.2 .

@tcharding
Copy link
Member Author

I was going to open an issue and discussion and what not but since this is pretty basic I thought I'd just learn how to contribute to rust-secp245k1 and open a PR. Please feel free to be as critical as you like, I'm here to learn.

src/key.rs Outdated

/// Secret 256-bit key used as `x` in an ECDSA signature
#[cfg_attr(feature = "zeroize_derive", derive(Zeroize))]
Copy link
Contributor

Choose a reason for hiding this comment

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

Given how trivial the generated implementation is, we might want to implement this ourselves so we don't add syn to this dependency tree? (Assuming none of the other dependencies already pull it in but I don't think so)

Copy link
Member Author

Choose a reason for hiding this comment

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

Has the added bonus that we can use zeroize for the feature name :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Is 'no new dependencies' a hard rule @apoelstra ? I don't see how to do this without adding the dependency on zeroize.

Copy link
Contributor

Choose a reason for hiding this comment

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

Has the added bonus that we can use zeroize for the feature name :)

FWIW: You could have also achieved this by enabling the custom derive support through the feature that is exposed by zeroize instead of depending on zeroize_derive directly: https://docs.rs/zeroize/1.2.0/zeroize/#custom-derive-support

zeroize = { version = "1.2", default-features = false, optional = true, features = ["zeroize_derive"] }

This will re-export the Zeroize derive from the zeroize crate as zeroize::Zeroize. Serde uses the same pattern so you don't have to depend on serde and serde_derive at the same time.

Copy link
Member Author

Choose a reason for hiding this comment

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

Cool, cheers man.

@apoelstra
Copy link
Member

apoelstra commented Dec 22, 2020

Agree with @thomaseizinger, we might as well generate this ourselves (especially as we already have macros which do a pile of stuff for byte arrays...might as well impl Zeroize on all of them).

Also need to add a note to the README that if you enable this feature the MSRV is 1.44, since normally it's 1.29. And I guess CI needs another case (I'd be fine if we just tacked testing this onto the nightly test case, since we don't actually use the code we only need to build-test it.)

@real-or-random
Copy link
Collaborator

We rejected zeroize in the past, see the discussion in #102 .

We can of course reconsider this.

@apoelstra
Copy link
Member

@real-or-random well, this PR is far more limited in scope than the previous one. We aren't implementing Drop or unimplementing Copy or anything, we're just giving users who want to use zeroize the ability to use it with our types.

@real-or-random
Copy link
Collaborator

Ok, I see. This makes sense then!

@tcharding
Copy link
Member Author

Perhaps we should not be implementing Zerioze for SecretKey at all since doing so implies that the library correctly handles zeroing secrets. I personally am not comfortable being responsible for making that statement since I do not know enough about how the compiler works to guarantee that the call chain let (sk, _) = generate_keypair() -> SecretKey::new() -> random_32_bytes() will not leave any secret data in memory if the user calls sk.zeroize().

This makes me think that all the zeroing I've done outside of this library is just smoke and mirrors :)

@elichai
Copy link
Member

elichai commented Dec 23, 2020

Perhaps we should not be implementing Zerioze for SecretKey at all since doing so implies that the library correctly handles zeroing secrets. I personally am not comfortable being responsible for making that statement since I do not know enough about how the compiler works to guarantee that the call chain let (sk, _) = generate_keypair() -> SecretKey::new() -> random_32_bytes() will not leave any secret data in memory if the user calls sk.zeroize().

This makes me think that all the zeroing I've done outside of this library is just smoke and mirrors :)

Yeah, if you want to do best efforts you need to do one of 2 things:

  1. Box the secret on the heap, and only use the box/reference and zero at the end.
  2. Make sure that every stack instance is zeroed. this includes "moved" memory (it's actually debatable if writing into moved memory is UB or not in rust)

And even after these 2 you have 2 problems:

  1. At the "math" level, you need to make sure that scalars/points/limbs everything is zeroed correctly and there are no copies/moves left unzeroed.
  2. The compiler can still introduce copies that are invisible to your code, which you obviously can't zero out. so you should probably audit the assembly.

EDIT: IMHO even though that last point seems like this is meaningless I don't agree. I think even zeroing some out the copies is better than none, because every copy makes a Heartbleed-like attack a little easier.

@tcharding
Copy link
Member Author

EDIT: IMHO even though that last point seems like this is meaningless I don't agree. I think even zeroing some out the copies is better than none, because every copy makes a Heartbleed-like attack a little easier.

So, if I'm understanding correctly; zero what we can, gather as much information as we can get about things we might be missing and document that somewhere so its explicit what we've done and where there might be holes still. And doing so adds value to users of the library even if its not perfect. I"m on it!

@tcharding tcharding force-pushed the zeroize branch 2 times, most recently from 9adf33b to 1849214 Compare January 5, 2021 03:39
@tcharding tcharding marked this pull request as ready for review January 5, 2021 03:49
@elichai
Copy link
Member

elichai commented Jan 11, 2021

Concept ACK

@tcharding tcharding changed the title Add feature zeroize_derive Add feature zeroize Jan 20, 2021
Introduce new dependency `zeroize`. Implement `Zeroize` for our array
types with the `impl_array_newtype` macro. Add a unit test that test a
newly create `SecretKey` is zeroized after calling `zeroize()` on it.
@dr-orlovsky
Copy link
Contributor

Alternative impl without new dependency: #311

@tcharding
Copy link
Member Author

Feel free to close this if #311 goes in.

@tcharding
Copy link
Member Author

Seems like @dr-orlovsky has this feature covered (#312 and #313).

@tcharding tcharding closed this Jul 29, 2021
@elichai
Copy link
Member

elichai commented Oct 28, 2021

I just found a big problem with ever implementing Zeroize on SecretKey. it allows you to zero out a secret using safe rust, making this possible:

let secp = Secp256k1::new();
let mut secret_key = SecretKey::from_slice(&[0xcd; 32]).expect("32 bytes, within curve order");
let message = Message::from_slice(&[0xab; 32]).expect("32 bytes");
secret_key.zeroize();
let sig = secp.sign(&message, &secret_key);

This will cause an abort and violate libsecp256k1's API rules.

@tcharding tcharding deleted the zeroize branch January 11, 2022 04:46
@elichai elichai mentioned this pull request Dec 9, 2022
@kayabaNerve
Copy link

Necro-comment, yet a custom zeroize impl could call zeroize (writing zeroes without being optimized out if not further used). and then write all 1s. This will create a valid secret key, and if the key is used after zeroizing, the write 1s won't be optimized out.

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.

7 participants