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

fix: address points in issue #4138 and companions #4336

Merged
merged 24 commits into from
Jul 28, 2022

Conversation

jorgeantonio21
Copy link
Contributor

@jorgeantonio21 jorgeantonio21 commented Jul 22, 2022

Description
--- Fix issues #4138, #4333 and #4170.

Motivation and Context
--- Add domain separation and cipher authentication for out bound messaging

How Has This Been Tested?
--- Unit tests.

Comment on lines 52 to 53
const DOMAIN_SEPARATION_CHALLENGE_LABEL: &str = "com.tari.comms.dht.crypt.challenge";
const DOMAIN_SEPARATION_KEY_MESSAGE_LABEL: &str = "com.tari.comms.dht.crypt.challenge";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Domain separators must be unique.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely, changed !


/// Generates a Diffie-Hellman secret `kx.G` as a `chacha20::Key` given secret scalar `k` and public key `P = x.G`.
pub fn generate_ecdh_secret<PK>(secret_key: &PK::K, public_key: &PK) -> CipherKey
pub fn generate_ecdh_secret<PK>(secret_key: &PK::K, public_key: &PK) -> [u8; 32]
Copy link
Collaborator

Choose a reason for hiding this comment

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

This encoded output size is specific to the elliptic curve group used, and does not hold generically.

where PK: PublicKey + DiffieHellmanSharedSecret<PK = PK> {
// TODO: PK will still leave the secret in released memory. Implementing Zerioze on RistrettoPublicKey is not
// currently possible because (Compressed)RistrettoPoint does not implement it.
let k = PK::shared_secret(secret_key, public_key);
CipherKey(*Key::from_slice(k.as_bytes()))
let mut output = [0u8; 32];
Copy link
Collaborator

Choose a reason for hiding this comment

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

This encoded size is specific to the elliptic curve group used, and is not generic.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It might be useful to define a generic DiffieHellmanSharedSecret type that holds the result of an ECDH exchange.

Copy link
Member

Choose a reason for hiding this comment

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

@jorgeantonio21 Would like to have the well-defined DH return type, perhaps for a follow-up PR?

.finalize()
.into_vec();

// Domain separation uses Challenge = Blake256, thus its output has 32-byte length
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is possible to check and enforce this at compile time, in case later changes subtly break this?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The use of a specific ECDH shared secret type would mean this function enforces this input type.

.finalize()
.into_vec();

// Domain separation uses Challenge = Blake256, thus its output has 32-byte length
Copy link
Collaborator

@AaronFeickert AaronFeickert Jul 22, 2022

Choose a reason for hiding this comment

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

Is it possible to check and enforce this at compile time, in case later changes subtly break this?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The use of a specific ECDH shared secret type would mean this function enforces this input type.

Copy link
Member

Choose a reason for hiding this comment

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

It is possible to check and enforce this at compile time, in case later changes subtly break this?

I believe so. Noting that this will not really break sublty and will panic in Key::from_slice if not 32-bytes. There are many tests that would signal this breakage, so we can decide if the following is worthwhile.

digest::Output<D> returned from finalize implements Into<[u8;32]> for 32-byte outputs - this could be used to generate a compile error if the DomainSeparatedHash<D> type exposed the inner output type e.g into_inner(self) -> Output<D>

 let domain_separated_hash =
        DomainSeparatedHasher::<Blake256, GenericHashDomain>::new(DOMAIN_SEPARATION_KEY_SIGNATURE_LABEL)
            .chain(data)
            .finalize();
// Will not compile if Into<[u8;32]> is not implemented.
let output_array : [u8;32] = domain_separated_hash.into_inner().into();

This requires adding the into_inner function or reimplementing (something like) From<[u8;D::OutputSize::to_usize()]> on DomainSeparatedHash.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@sdbondi How generic can that be made? It may be the case that other domain-separated hash function applications require output of a different length.

Copy link
Member

@sdbondi sdbondi Jul 28, 2022

Choose a reason for hiding this comment

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

@AaronFeickert That is handled by the generic_array crate. Here, we are enforcing in this function (or else compile fails) that the hash output is correctly convertible to a 32-byte array representation.

Adding trait bounds are not necessary here as we use concrete types, but the trait bounds would be where digest::Output<D>: Into<[u8;32]>

This became possible with this change (tari-project/tari-crypto#119) since we can now use digest::Output<D>

let domain_separated_hash = DomainSeparatedHasher::<Blake256, GenericHashDomain>::new(DOMAIN_SEPARATION_KEY_SIGNATURE_LABEL) .chain(data) .finalize();
 // Will not compile if Into<[u8;32]> is not implemented. 
let output_array : [u8;32] = domain_separated_hash.into();

Copy link
Member

Choose a reason for hiding this comment

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

So as the tari_crypto domain hasher code currently stands, you'd need to disambiguate between the functions on the hasher type and the impl Digest functions with the same name.
e.g.

     let hasher = comms_dht_hash_domain_key_signature().chain(data);
    let domain_separated_hash =<DomainSeparatedHasher<CommsChallenge, DHTCommsHashDomain> as Digest>::finalize(hasher);
    let hash: [u8; 32] = domain_separated_hash.into();

Since we know that we're using Blake256 and breakage will be caught in tests - I think we can leave this as is

/// Produces authenticated encryption of the signature using the ChaCha20-Poly1305 stream cipher,
/// refer to https://docs.rs/chacha20poly1305/latest/chacha20poly1305/# for more details.
/// Attention: as pointed in https://github.com/tari-project/tari/issues/4138, it is possible
/// to use a fixed length Nonce, with homogeneous zero data, as this does not incur any security
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not a fixed-length nonce, but a fixed nonce.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely ! Thank you !

@jorgeantonio21 jorgeantonio21 changed the title Draft: address points in issue #4138 and companions fix: address points in issue #4138 and companions Jul 22, 2022
.map_err(|_| DhtOutboundError::CipherError(String::from("Authenticated encryption failed")))
.unwrap();

assert_eq!(encrypted.len(), n + 16);
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could remove the magic number by getting the tag size programmatically.

@jorgeantonio21 jorgeantonio21 marked this pull request as ready for review July 28, 2022 11:02
Copy link
Member

@sdbondi sdbondi left a comment

Choose a reason for hiding this comment

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

Ack

Tested with memorynet

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