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

feat: improve hidden data handling #52

Merged
merged 14 commits into from
Nov 18, 2022

Conversation

AaronFeickert
Copy link
Contributor

@AaronFeickert AaronFeickert commented Nov 8, 2022

Some data, like cryptographic key material or passwords, needs special handling. Such data typically should:

  • be zeroized when it goes out of scope
  • never be displayed or written to debug logs
  • not typically be accessed except by (mutable) reference
  • have a strictly enforced type to avoid misuse

This PR updates the Hidden type to handle this kind of data in a generic way. Hidden types can be instantiated using any underlying type that implements Zeroize and an optional differentiated type created by a macro that enforces context and prevents misuse.

The hidden data is stored on the heap in a Box wrapper. This allows us to safely zeroize it on drop, and automatically prevents display and debug from revealing it unintentionally. Further, we only expose access to the data via immutable and mutable reference. The implementation supports Clone for cases where we need it, but does not support Copy.

We also update SafePassword to be an instantiation of the updated Hidden type. This refactor is transparent to callers, as its existing API is unchanged.

src/clandestine.rs Outdated Show resolved Hide resolved
src/clandestine.rs Outdated Show resolved Hide resolved
src/clandestine.rs Outdated Show resolved Hide resolved
src/clandestine.rs Outdated Show resolved Hide resolved
@jorgeantonio21
Copy link

Pretty cool !

LGTM

Copy link
Contributor

@CjS77 CjS77 left a comment

Choose a reason for hiding this comment

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

I'm not convinced this is the right approach.

There's some debate going on in another fork of this repo on the same topic that I'll copy over here, when I have a spare minute.

We'll get there though.

AaronFeickert referenced this pull request in AaronFeickert/tari-crypto Nov 9, 2022
@AaronFeickert
Copy link
Contributor Author

AaronFeickert commented Nov 9, 2022

Significantly updated. The latest commit instead updates Hidden to add generic type support, optional type enforcement, and zeroizing on drop. It also refactors SafePassword to use this, but in a way that maintains its API.

@AaronFeickert AaronFeickert changed the title feat: clandestine data handling feat: improve hidden data handling Nov 9, 2022
src/hidden.rs Outdated Show resolved Hide resolved
src/password.rs Outdated Show resolved Hide resolved
src/hidden.rs Outdated
Comment on lines 36 to 43
macro_rules! hidden_label {
($name:ident) => {
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct $name;

impl HiddenLabel for $name {}
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Something about the macro feels like an anti-pattern to me. From a usability perspective to have the marker trait applied I would want to throw it into derive. The use of hidden_label!() is offputting for a reason I can't quite put my finger on.

Copy link
Contributor Author

@AaronFeickert AaronFeickert Nov 10, 2022

Choose a reason for hiding this comment

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

I'm not a huge fan of the approach, and I wish there were a better way to do what we want (enforced type differentiation) in Rust without resorting to replicating boilerplate code in a way that's likely to be done wrong. For what it's worth, @jorgeantonio21 suggested replacing the whole macro construction with a const str in the generic type definition, but Rust doesn't support it. I'm definitely open to a different approach if we can find one that works.

Possibly worth noting that a similar approach is used in both the new hashing and Schnorr signature APIs to enforce type differentiation and enable certain hash operations to do what we want.

src/hidden.rs Outdated
// A default hidden type label; only use this if you're absolutely sure you don't care about type enforcement
hidden_label!(DefaultHiddenLabel);

/// Data that needs to be kept hidden, needs to be zeroized when it goes away, and is resistant to misuse
Copy link

@jorgeantonio21 jorgeantonio21 Nov 10, 2022

Choose a reason for hiding this comment

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

Perhaps add an example to docs. Something like

Suggested change
/// Data that needs to be kept hidden, needs to be zeroized when it goes away, and is resistant to misuse
/// Data that needs to be kept hidden, needs to be zeroized when it goes away, and is resistant to misuse.
///
/// The following will throw a compile error:
///
/// hidden_label!(MyFirstLabel);
/// hidden_label!(MySecondLabel);
///
/// type HiddenType1 = Hidden<[u8; 3], MyFirstLabel>;
/// type HiddenType2 = Hidden<[u8; 3], MySecondLabel>;
///
/// fn accept_only_hidden_type_2(hidden: HiddenType2) {
/// println!("My dear hidden type 2");
/// }
///
/// let hidden_1 = HiddenType1::hide([0u8, 1, 2]);
/// accept_only_hidden_type_2(hidden_1);

Copy link

@jorgeantonio21 jorgeantonio21 left a comment

Choose a reason for hiding this comment

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

I am pretty satisfied with the current state. I left a few minor comments.

@brianp pointed out that the use of the hidden_label macro is not clear for a developer without the right context, there might be a way to satisfy our goal of type enforcement in a simpler way.

Nonetheless, I consider having type enforcement is a very important feature to have.

jorgeantonio21
jorgeantonio21 previously approved these changes Nov 11, 2022
Copy link

@jorgeantonio21 jorgeantonio21 left a comment

Choose a reason for hiding this comment

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

Everything we discussed was addressed, I am in favor of it.

LGTM

@AaronFeickert
Copy link
Contributor Author

Can someone enable CI workflows, so we can check for any problems there?

Copy link

@jorgeantonio21 jorgeantonio21 left a comment

Choose a reason for hiding this comment

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

LGTM :)

@AaronFeickert
Copy link
Contributor Author

Updated to address some implementation concerns that primarily concern derived traits and trait bounds:

  • Documentation is improved.
  • Hidden now derives only Clone, Serialize, and Deserialize, and no longer has a Default trait bound on its underlying type.
  • The hidden_type! macro generates a struct deriving only Clone, Debug, and Zeroize, and no longer has a Zeroize trait bound on its underlying type (it's enforced in Hidden).
  • SafePassword now only derives Clone, Debug, Serialize, and Deserialize.

This means we can now use the macro with types that don't support serialization or defaults, which can include even basic types like [u8; 64] that we may need to use for derived keys. If we need serialization on such types, we can define them separately and derive the required traits.

It also means that the SafePassword API changes in a way that affects its tari use. Namely, that crate performs equality testing on SafePassword instances directly, which is probably bad form. This can be trivially changed (in only two locations) to testing on reveal() references, which seems much more idiomatic.

stringhandler pushed a commit to tari-project/tari that referenced this pull request Nov 16, 2022
Description
---
Minor updates for a corresponding [change](tari-project/tari_utilities#52) to the `SafePassword` API.

Motivation and Context
---
A pending change in `tari_utilities` to the handling of sensitive data includes a new implementation of `SafePassword`. One change removes derived traits for equality testing in favor of equality testing on references to the underlying passphrase data. This work makes the minor but necessary changes to address this API change.

How Has This Been Tested?
---
Tests pass after applying the linked `tari_utilities` PR. Note that tests will pass using the current `SafePassword` API as well, as the dependency change PR restricts the API.
Copy link
Contributor

@CjS77 CjS77 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. A few nits, and I suggest we remove serialisation support

src/hidden.rs Outdated Show resolved Hide resolved
src/password.rs Outdated Show resolved Hide resolved
src/hidden.rs Outdated Show resolved Hide resolved
src/hidden.rs Outdated Show resolved Hide resolved
}

/// Provides access to the inner value (mutable).
/// Reveal the hidden data as a mutable reference

Choose a reason for hiding this comment

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

Would be good to have a description on why this is safe, given the way Box is treated by the compiler when dereferenced. Same for reveal.

Copy link
Contributor Author

@AaronFeickert AaronFeickert Nov 16, 2022

Choose a reason for hiding this comment

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

From the analysis @CjS77 did on the compiled version, it's unclear precisely if/where this can go wrong. My understanding was that asterisk dereferencing of a Box<T> can perform a move, but need not do so in all cases. One apparent way to avoid this may be to replace any asterisk dereferencing with explicit calls to <Box<T> as Deref>::deref instead, but I have not tested this (and I'm not sure if the wrapped reference calls we use avoid this problem regardless).

@AaronFeickert
Copy link
Contributor Author

All tari tests pass using this version of tari_utilities locally.

@AaronFeickert
Copy link
Contributor Author

AaronFeickert commented Nov 16, 2022

To mitigate dereference woes, added a new SafeArray<T, const N: usize> type that acts like an array by implementing AsRef<[T]> and AsMut<[T]>, but does not implement Copy.

Since it's intended for keys, also implements Default and ConstantTimeEq, and overrides Eq to be a constant-time operation too.

You'd use it by defining a hidden type in this manner: hidden_type!(CipherKey, SafeArray<u8, 32>)

Then you can use as_ref() or as_mut() to access it like an array reference, but it can't be dereferenced.

Comments welcome on this idea.

Copy link
Contributor

@CjS77 CjS77 left a comment

Choose a reason for hiding this comment

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

I think this is as good as we can make it for now.

It would be interesting to see some performance benchmarks for secret key and ECC operations based on this (Vec<u8>) vs [u8; 32]

One day when #[sensitive] attributes can be passed to LLVM (something that is being discussed but not on any roadmap afaict), we can come and revisit this approach

src/safe_array.rs Outdated Show resolved Hide resolved
jorgeantonio21
jorgeantonio21 previously approved these changes Nov 17, 2022
Copy link

@jorgeantonio21 jorgeantonio21 left a comment

Choose a reason for hiding this comment

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

LGTM, I left a minor comment. Let's also run cargo fmt.

stringhandler added a commit to tari-project/tari that referenced this pull request Nov 17, 2022
* fix: updates for SafePassword API change (#4927)

Description
---
Minor updates for a corresponding [change](tari-project/tari_utilities#52) to the `SafePassword` API.

Motivation and Context
---
A pending change in `tari_utilities` to the handling of sensitive data includes a new implementation of `SafePassword`. One change removes derived traits for equality testing in favor of equality testing on references to the underlying passphrase data. This work makes the minor but necessary changes to address this API change.

How Has This Been Tested?
---
Tests pass after applying the linked `tari_utilities` PR. Note that tests will pass using the current `SafePassword` API as well, as the dependency change PR restricts the API.

* chore: remove unused methods (#4922)

Delete some unused methods

* v0.40.0

* fix: set wallet start scan height to birthday and not 0 (see issue #4807) (#4911)

Description
---
When wallet starts new UTXO scanning service it should start at the birthday. Currently, it starts at height 0. This PR addresses this issue

Motivation and Context
---
Addresses #4807.

How Has This Been Tested?
---

* chore: fix naming of ffi functions and comments  (#4930)

Description
---
Fixing the comments of one function in the wallet FFI.
Fixes the names of 4 functions in the wallet FFI.

* chore: fix depreciated timestamp clippy (#4929)

Description
---
Fixes depreciated timestamp warnings

* feat: implement validator node lmdb store

* feat: add stateless vn reg utxo validation rules and merkle root

* rename vn validaty period consensus constant

* use commitment instead of block hash so that lmdb uses canonical ordering

* DRY up vn_mmr calculation, add vn reg utxo validations

* fix clippies

* wallet: use amount from consensus constants for VN reg

* adds validator node signature to common types

This is to allow the validator node to use the same signature

* remove commitment from signature for now

* Remove unneeded validator node kernel feature

* ad epoch_length to grpc consensus constants

* fix epoch interval check and add test

* reduce target time for igor

* clippy

* change shard key hash label

Co-authored-by: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com>
Co-authored-by: stringhandler <mikethetike@tari.com>
Co-authored-by: jorgeantonio21 <matroid@outlook.com>
Co-authored-by: SW van Heerden <swvheerden@gmail.com>
@AaronFeickert
Copy link
Contributor Author

It would be interesting to see some performance benchmarks for secret key and ECC operations based on this (Vec<u8>) vs [u8; 32]

I wouldn't use this for elliptic curve SecretKey operations, as I'm sure that's an entirely different can of worms to open. But for hash-based derived keys, I would be surprised if it's much of a practical difference since those are used more sparingly (and the hash function computation time will surely dominate). Fortunately it would be easy to change the guts of SafeArray in a non-breaking way later if desired.

@AaronFeickert
Copy link
Contributor Author

LGTM, I left a minor comment. Let's also run cargo fmt.

Yep, will run a few more proof-of-concept tests locally to ensure this will meet our needs, and then format to make the linter happy.

@AaronFeickert
Copy link
Contributor Author

AaronFeickert commented Nov 17, 2022

Updated to implement Deref and DerefMut with Target = [T], which makes it easier to use with the hashing API.

Would appreciate if someone can verify that you still can't dereference the underlying data. I ran into sizing errors while trying to do this.

Also would like comment on whether the SafeArray abstraction is too much effort. Presumably you can still dereference individual elements with an implicit copy happening? If it's just another abstraction that ends up causing more work for implementers while not sufficiently reducing risk, we could nix it.

@stringhandler stringhandler merged commit f35006f into tari-project:main Nov 18, 2022
@AaronFeickert AaronFeickert deleted the clandestine branch November 18, 2022 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants