Skip to content

feat(dstack-util): add streaming encrypt and decrypt commands - #1054

Open
kvinwang wants to merge 9 commits into
nextfrom
feat/dstack-util-decrypt
Open

feat(dstack-util): add streaming encrypt and decrypt commands#1054
kvinwang wants to merge 9 commits into
nextfrom
feat/dstack-util-decrypt

Conversation

@kvinwang

@kvinwang kvinwang commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

The CVM encrypted-environment path only decrypts one monolithic blob during setup. Applications that deliver encrypted data through another channel have no reusable guest-side decrypt command, and the legacy single-tag AES-GCM format requires buffering the complete message.

Fix

  • add dstack-util encrypt, which retrieves the app-scoped environment encryption public key from KMS
  • require --kms-pubkey and verify the KMS timestamped signature_v1 before encrypting
  • add a magic-prefixed, versioned chunk format with independently authenticated AES-256-GCM frames
  • add dstack-util decrypt, which auto-detects chunked ciphertext while retaining legacy encrypted-env compatibility
  • support stdin/files, configurable chunk sizes, custom KMS root CAs, and legacy hex ciphertext input
  • normalize KMS URLs with or without trailing /prpc/
  • document the wire format and streaming failure semantics

Each frame authenticates the stream header, chunk index, flags, and plaintext length. The decoder rejects truncation, reordering, unknown flags, oversized frames, authentication failures, and trailing data. Because authenticated chunks are emitted incrementally, callers must discard output whenever the command exits unsuccessfully.

Testing

  • cargo clippy -p dstack-util -- -D warnings -D clippy::expect_used -D clippy::unwrap_used --allow unused_variables
  • cargo test -p dstack-util (87 passed)
  • round trips across multiple chunks
  • tampered and truncated ciphertext rejection
  • magic auto-detection and legacy fallback
  • hex input decoding and URL normalization
  • trusted signer recovery and untrusted signer rejection

Copilot AI lite review requested due to automatic review settings August 14, 2026 02:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends dstack-util (the guest utility CLI) with a new decrypt subcommand for decrypting data encrypted using the app environment encryption scheme, reusing the app’s env_crypt_key from the app keys file.

Changes:

  • Added a dstack-util decrypt subcommand wired into the CLI command enum and main() dispatch.
  • Implemented cmd_decrypt to read ciphertext from stdin or a file, optionally hex-decode, decrypt via crypto::dh_decrypt, and write plaintext to stdout or a mode-0600 file.
  • Added CLI flags for --input, --output, --key-file, and --hex.
Suppressed comments (1)

dstack/dstack-util/src/main.rs:698

  • For consistency with other error messages in this crate, this error should start lowercase.
    let env_crypt_key: [u8; 32] = keys
        .env_crypt_key
        .try_into()
        .map_err(|key: Vec<u8>| anyhow::anyhow!("Invalid env crypt key length: {}", key.len()))?;

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread dstack/dstack-util/src/main.rs Outdated
Comment thread dstack/dstack-util/src/main.rs Outdated
@kvinwang kvinwang changed the title feat(dstack-util): add decrypt command feat(dstack-util): add streaming encrypt and decrypt commands Aug 14, 2026
@kvinwang

Copy link
Copy Markdown
Collaborator Author

Addressed in 88bb69d48.

  • encrypt now requires --kms-pubkey and verifies the timestamped signature_v1 against that trusted secp256k1 signer, including the 300-second default freshness window and 60-second future-skew allowance. It fails closed on missing, malformed, stale, or untrusted signatures.
  • URL normalization now trims trailing slashes before detecting /prpc; this fixes both encrypt and get-keys.
  • The format documentation now states that streaming output can contain an authenticated but incomplete prefix on failure and that callers must check the exit status and discard output.
  • New error messages use lowercase initial letters.
  • Added tests for magic auto-detection, legacy fallback, hex decoding, URL normalization, valid signer recovery, and untrusted signer rejection.

Verified with the repository strict clippy invocation and all 87 dstack-util tests.

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.

2 participants