Skip to content

stack-encrypt: derive ValueFrame's constant-time PartialEq once vitaminc can compare a Vec<u8> #1542

Open

Description

@coderdan

Blocked by cipherstash/vitaminc#602.

Background

stack-encrypt is the Rust engine in this repository that encrypts each value under its own key. Its dynamic API passes each value as a ValueFrame. A ValueFrame stores one value's bytes in vitaminc's transport format, called a frame.

vitaminc is CipherStash's Rust library of cryptographic building blocks. Its vitaminc-protected crate has wrapper types for secrets. A wrapper wipes its secret when it is dropped. Equatable<T> is the wrapper for a secret that can be compared, in constant time.

Problem

ValueFrame stores its frame in an Equatable<Protected<Vec<u8>>>, since #1501. But Equatable cannot compare a Vec<u8> yet. vitaminc-protected 0.9.1 has no ConstantTimeEq impl for Vec<u8>. The trait is sealed, so this crate cannot add one. cipherstash/vitaminc#602 adds it.

Until then, ValueFrame's PartialEq is written by hand, in packages/stack-encrypt/src/dynamic/value_frame.rs. It reads both frames' bytes and compares them with TimingSafeEq for [u8]. That impl calls subtle. The comparison is constant-time and safe. It is a workaround, though: it reads the bytes itself, where Equatable would compare them without the crate reading them.

Proposal

When cipherstash/vitaminc#602 is released, expected as vitaminc-protected 0.9.2:

  1. Move the vitaminc-protected pin to the new version in the root Cargo.toml and in packages/stack-encrypt/fuzz/Cargo.toml. Then regenerate each lockfile that cargo metadata --locked refuses. Check the root, both Go guests, EQL, the three fuzz crates, and the EQL timing harness's ctgen.
  2. In value_frame.rs, delete the hand-written impl PartialEq for ValueFrame and its comment, and derive it: #[derive(OpaqueDebug, PartialEq)]. Keep the doc that says what equal means, on the struct or on the derive.
  3. Keep the four tests in the equality module of value_frame.rs. They must pass unchanged: equal frames, a one-byte difference, different lengths, and 34u32 against 34u64.
  4. Check that the three doc examples that compare frames still pass. They are in value_frame.rs (module docs), record.rs (record::encrypt), and kind.rs (dynamic::read).
  5. Update the CHANGELOG entry that names the workaround.

Metadata

Metadata

Assignees

No one assigned

    Labels

    SDKenhancementNew feature or requestrustPull requests that update Rust code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions