Rage Versions Save

A simple, secure and modern file encryption tool (and Rust library) with small explicit keys, no config options, and UNIX-style composability.

v0.10.0

3 months ago

rage

Added

  • Russian translation!
  • rage-keygen -y IDENTITY_FILE to convert identity files to recipients.
  • Elvish completions to the Debian package. These are not automatically discovered; Elvish users will need to manually import them.
  • Localized manpages to the Debian package.

Changed

  • MSRV is now 1.65.0.
  • Migrated from gumdrop to clap for argument parsing.
  • -R/--recipients-file and -i/--identity now support "read-once" files, like those used by process substitution (-i <(other_binary get-age-identity)) and named pipes.
  • The filename - (hyphen) is now treated as an explicit request to read from standard input when used with -R/--recipients-file or -i/--identity. It must only occur once across the -R/--recipients-file and -i/--identity flags, and the input file. It cannot be used if the input file is omitted.

Fixed

  • OpenSSH private keys passed to -i/--identity that contain invalid public keys are no longer ignored when encrypting, and instead cause an error.
  • Weak ssh-rsa public keys that are smaller than 2048 bits are now rejected.
  • rage-keygen no longer overwrites existing key files with the -o/--output flag. This was its behaviour prior to 0.6.0, but was unintentionally changed when rage was modified to overwrite existing files. Key file overwriting can still be achieved by omitting -o/--output and instead piping stdout to the file.
  • rage-keygen now prints fatal errors directly instead of them being hidden behind the RUST_LOG=error environment variable. It also now sets its return code appropriately instead of always returning 0.
  • The Debian package now uses the correct installation paths for fish and Zsh completions.

age

Added

  • Russian translation!
  • age::cli_common:
    • file_io:
      • FileReader
      • impl Debug for {LazyFile, OutputFormat, OutputWriter, StdoutWriter}
    • StdinGuard
    • read_recipients
  • age::identity::IdentityFile::from_input_reader (behind cli-common feature flag).
  • impl Eq for age::ssh::{ParseRecipientKeyError, UnsupportedKey}
  • impl {Debug, PartialEq, Eq, Hash} for age::x25519::Recipient

Changed

  • MSRV is now 1.65.0.
  • Migrated to base64 0.21, rsa 0.9.
  • age::cli_common:
    • file_io:
      • InputReader::File enum variant now contains FileReader instead of std::fs::File.
      • OutputWriter::new now takes an allow_overwrite boolean argument. If OutputWriter will write to a file, this boolean enables the caller to control whether the file will be overwritten if it exists (instead of the implicit behaviour that was previously changed in 0.6.0).
    • read_identities now takes an &mut StdinGuard argument, and filenames may now contain at most one entry of "-", which will be interpreted as reading from standard input.
    • ReadError has new variants:
      • EncryptedIdentities
      • InvalidRecipient
      • InvalidRecipientsFile
      • MissingRecipientsFile
      • MultipleStdin
      • RsaModulusTooLarge
      • RsaModulusTooSmall
  • age::ssh:
    • ParseRecipientKeyError has new variants:
      • RsaModulusTooLarge
      • RsaModulusTooSmall
    • The following trait implementations now return Err(ParseRecipientKeyError::RsaModulusTooLarge) instead of Err(ParseRecipientKeyError::Unsupported(_)) when encountering an RSA public key with a modulus larger than 4096 bits:
      • impl FromStr for Recipient
      • impl TryFrom<Identity> for Recipient

Fixed

  • age::Encryptor::with_user_passphrase will now re-measure the scrypt work factor until it is measurable, instead of setting the work factor to maximum.
  • age::cli_common:
    • UiCallbacks::confirm no longer requires erasing the confirmation message before it will accept a response.
    • UiCallbacks::request_public_string no longer prepends the description to the response string.
  • Weak ssh-rsa public keys that are smaller than 2048 bits are now rejected from all string-parsing APIs. The Recipient::SshRsa enum variant can still be manually constructed with such keys; this will be fixed in a future crate refactor.

age-plugin 0.5.0

Changed

  • MSRV is now 1.65.0.
  • Migrated to age-core 0.10.
  • age_plugin::run_state_machine now takes optional arguments, to enable the creation of recipient-only or identity-only plugins.

age-core

Added

  • impl Eq for age_core::format::Stanza

Changed

  • MSRV is now 1.65.0.

New Contributors

Full Changelog: https://github.com/str4d/rage/compare/v0.9.2...v0.10.0

v0.9.2

10 months ago

rage

Changed

  • Increased parsing speed of age file headers. For single-recipient encrypted decryption throughput increases by 6% for medium (< 1MiB) files, and 40% for small (< 10kiB) files.
  • The pinentry binary used to request passphrases can now be set manually with PINENTRY_PROGRAM environment variable. It accepts either a binary name a path. Setting this to the empty string will disable pinentry usage and back to the CLI interface.
  • Linux release binaries are now built using Ubuntu 20.04.

age

Added

  • age::Decryptor::{new_buffered, new_async_buffered}, which are more efficient types implementing std::io::BufRead or futures::io::AsyncBufRead includes &[u8] slices).
  • impl std::io::BufRead for age::armor::ArmoredReader
  • impl futures::io::AsyncBufRead for age::armor::ArmoredReader

Changed

  • The pinentry binary used by age::cli_common::read_secret can now be set with the PINENTRY_PROGRAM environment variable. It accepts either a name or a path. Setting this to the empty string will disable pinentry and fall back to the CLI interface.

Fixed

  • The AsyncWrite::poll_write implementation for age::stream::StreamWriter never returns 0 if there is data to write. This makes StreamWriter with futures::io::copy.

v0.9.1

1 year ago

rage

Added

  • Support for encrypted OpenSSH keys exported from 1Password.

age

Added

  • Support for encrypted OpenSSH keys exported from 1Password.

v0.9.0

1 year ago

rage

Changed

  • MSRV is now 1.59.0.

Fixed

  • Encryption now returns an error if the file would be encrypted to no recipients. This can occur if only -R/--recipients-file flags are provided, and they all point to files that contain only "#" prefixed comments and empty lines.

age

Added

  • age::armor::ArmoredReadError, used to wrap armor-specific read errors inside std::io::Error.
  • age::ssh:
    • impl Clone for Identity

Changed

  • MSRV is now 1.59.0.
  • age::Encryptor::with_recipients now returns Option<Encryptor>, with None returned if the provided list of recipients is empty (to prevent files being encrypted to no recipients). The recipients argument is also now Vec<Box<dyn age::Recipient + Send>>.
  • age::encrypted::Identity::recipients now returns Vec<Box<dyn age::Recipient + Send>>.

Fixed

  • age::Decryptor now rejects invalid or non-canonical scrypt recipient stanzas (instead of ignoring or accepting them respectively), matching the age specification.
  • age::armor::ArmoredReader:
    • It now correctly implements strict parsing as defined in RFC 7468, and rejects armored files with non-canonical final lines (where padding bytes are omitted).
    • It now rejects armored files with non-whitespace characters after the end marker.
    • It now accepts armored files with no newline after the end marker. Previously these were rejected by the synchronous API, and would cause the async API to hang.
    • The async API now correctly rejects some classes of invalid armoring that previously would cause it to hang.

age-plugin 0.4.0

Changed

  • MSRV is now 1.59.0.
  • Migrated to age-core 0.9.

age-core

Changed

  • MSRV is now 1.59.0.
  • Migrated to aead 0.5.

v0.8.1

1 year ago

rage

Security

  • Require age 0.8.1.

age

Security

  • age::Decryptor did not previously require "contributory" behaviour for X25519 recipient stanzas. If an age file has an X25519 recipient stanza with an ephemeral share that is a small-order point, the file could previously be decrypted by any native age identity. To ensure we match the behaviour in the age specification, these files are now rejected as invalid.

Fixed

  • age::Decryptor now rejects invalid or non-canonical X25519 recipient stanzas (instead of ignoring or accepting them respectively), matching the age specification.

v0.8.0

2 years ago

rage

Changed

  • MSRV is now 1.56.0.
  • When both reading input from the terminal (e.g. if the user is typing the plaintext to be encrypted) and writing output to the terminal, rage now buffers the output until the input is finished, so the output doesn't get in the way of typing.
  • A warning is now displayed if rage detects that the file being encrypted starts with the age magic string or armor begin marker (indicating that an age-encrypted file is being double-encrypted). The file is still encrypted.
  • A message is now printed if a plugin takes longer than 10 seconds to encrypt or decrypt its header entry (for example, if the plugin is waiting on some user interaction that hasn't occurred yet).

Fixed

  • Decryption now returns an error when given a passphrase-encrypted file if -i/--identity is present. Previously this could result in scripts hanging forever (given that passphrase decryption is intentionally not scriptable).

age

Added

  • age::Callbacks::confirm to request that the user provides confirmation for some action.
  • age::cli_common::file_io::InputReader::is_terminal
  • age::ssh::ParseRecipientKeyError, which was previously in the public API but unnameable and could not be matched upon.

Changed

  • MSRV is now 1.56.0.
  • age::Callbacks now requires Clone + Send + Sync + 'static bounds.
  • age::cli_common::file_io::OutputWriter::new now takes an input_is_tty boolean argument. If input_is_tty is set to true, then if OutputWriter will write to a stdout TTY, it buffers the entire output so it doesn't get in the way of typing the input, and then writes the buffered output to stdout during OutputWriter::flush.
  • Ciphertexts are now required to end in a non-empty STREAM chunk, unless it is the only chunk (meaning that the plaintext is empty). Neither age nor rage generate non-empty files ending in an empty chunk, instead marking the final full chunk as the last chunk.

age-plugin 0.3.0

Added

  • age_plugin::Callbacks::confirm

Changed

  • MSRV is now 1.56.0.

age-core

Added

  • age_core::io::{DebugReader, DebugWriter}
  • age_core::plugin::Error::Unsupported
  • age_core::plugin::Reply::ok_with_metadata

Changed

  • MSRV is now 1.56.0.
  • age_core::plugin:
    • Connection::open now returns the debugging-friendly concrete type Connection<DebugReader<ChildStdout>, DebugWriter<ChildStdin>>.
    • BidirSend::{send, send_stanza} now return Ok(Error::Unsupported) when an unsupported response is received, instead of Err(io::Error), making it easier for plugins to implement fallback strategies.

v0.7.1

2 years ago

rage

Fixed

  • Fixed a bug in 0.7.0 where non-canonical recipient stanza bodies in an age file header would cause rage to crash instead of being rejected.

age

Fixed

  • Bumped age-core to 0.7.1 to fix a bug where non-canonical recipient stanza bodies in an age file header would cause a panic instead of being rejected.

age-plugin 0.2.1

Fixed

  • Bumped age-core to 0.7.1 to fix a bug where non-canonical recipient stanza bodies in an age file header would cause a panic instead of being rejected.

age-core

Fixed

  • In 0.7.0, Base64 decoding was moved to the AgeStanza::body method, with the stanza parser only checking for valid Base64 characters. This caused the parser to start accepting stanzas with non-canonical last body lines (where the Base64 encoding would have trailing bits that could not be decoded into full bytes); calling AgeStanza::body on these stanzas would cause a panic. This release fixes the parser to reject non-canonical last body lines, turning the panic back into an error.

v0.7.0

2 years ago

rage

Added

  • -i/--identity now accepts passphrase-encrypted age identity files.
  • The -j PLUGIN_NAME flag, which allows decrypting with a plugin using its "default mode" (in which no identity-specific information is required). This flag is equivalent to using -i/--identity with an identity file containing the default plugin identity (containing no data).

Changed

  • MSRV is now 1.51.0.
  • *-linux.tar.gz release binaries are now built with Ubuntu 18.04, and require a system with a minimum of glibc 2.27.

age

Added

  • age::encrypted::Identity, for decrypting files with passphrase-encrypted age identity files.
  • age::IdentityFileEntry enum, representing the possible kinds of entries within an age identity file.
  • age::{DecryptError, EncryptError, PluginError}: Clone bounds.
  • age::cli_common::UiCallbacks: Clone + Copy bounds.
  • age::cli_common::Passphrase::random, for generating a secure passphrase.
  • age::cli_common::ReadError
  • age::secrecy, which re-exports the secrecy crate.

Changed

  • MSRV is now 1.51.0.
  • age::IdentityFile::into_identities now returns Vec<IdentityFileEntry>.
  • age::cli_common::read_identities:
    • Encrypted age files will now be parsed and assumed to be encrypted age identities. This assumption is checked at file-decryption time.
    • New max_work_factor parameter for controlling the work factor when decrypting encrypted identities.
    • Identities are now returned in the same order as filenames (and top-to-bottom from within each file). Plugin identities are no longer coalesced; there is one Box<dyn Identity> per plugin identity.
    • age::cli_common::ReadError is now returned instead of a user-specified error type. The error constructor parameters have been removed from the function.
  • age::Callbacks::prompt has been renamed to Callbacks::display_message.
    • age::cli_common::UiCallbacks::display_message no longer uses pinentry (which displays a temporary prompt that can be dismissed), so the message is now part of the visible CLI output.

Removed

  • IdentityFile::split_into (replaced by IdentityFileEntry::Plugin).

age-plugin 0.2.0

Changed

  • MSRV is now 1.51.0.
  • age_plugin::Callbacks methods now return age_core::plugin::Error instead of () for internal errors, following changes to age_core::plugin::Result.

age-core

Added

  • age_core::secrecy, which re-exports the secrecy crate.
  • age_core::plugin::Error

Changed

  • MSRV is now 1.51.0.
  • The body property of age_core::format::AgeStanza has been replaced by the AgeStanza::body method, to enable enclosing parsers to defer Base64 decoding until the very end.
  • age_core::plugin::Result now only takes a single generic argument, and uses age_core::plugin::Error for its inner error type.

v0.6.0

3 years ago

rage

Added

  • Plugin support!
  • The -R/--recipients-file flag, which accepts a path to a file containing age recipients, one per line (ignoring "#" prefixed comments and empty lines).
  • The -e/--encrypt flag, to allow encryption to be an explicit choice (instead of relying on -d/--decrypt not being present).

Changed

  • MSRV is now 1.47.0.
  • -o/--output will now overwrite existing files instead of returning an error. This makes the behaviour consistent with most UNIX tools, as well as when using pipes.
  • Files encrypted with this version of rage might not decrypt with previous beta versions, due to changes in how stanza bodies are canonically encoded. This should only affect a small fraction of files (if grease that triggers the change is added, which has a 3% chance per file).
  • -r/--recipient now has the specific type "recipient" which better reflects its name, rather than the ambiguous "source of recipients" it was previously.
  • -i/--identity can now be used when encrypting files. This requires the -e/--encrypt flag (to prevent ambiguity, e.g. if the user wants to decrypt but forgets the -d/--decrypt flag).

Removed

  • Recipients file support from -r/--recipient (use -R/--recipients-file instead).
  • HTTPS support. This added otherwise-unnecessary networking dependencies to rage, and there are many decisions that need to be made when downloading a file (e.g. what roots to trust?) that go beyond the APIs we want to focus on here. Users should use a tool like curl or wget to download a recipients file, and then pass it to rage.
  • The unstable GitHub feature (which relied on HTTPS support).
  • The unstable aliases feature.

Fixed

  • Log output is now disabled by default, to prevent non-fatal error messages (such as an unset or invalid LANG variable) being printed to stderr while the program succeeds (which is confusing for users). The previous behaviour can be configured by setting the environment variable RUST_LOG=error.
  • Output files are now opened lazily, which avoids leaving behind an empty file when an error occurs before we write the header.

age

Security

  • StreamReader::seek(SeekFrom::End(offset)) did not previously authenticate the ciphertext length; if the ciphertext had been truncated or extended by adversary_offset, it would instead seek to offset + adversary_offset. This allowed an adversary with temporary control of an encrypted age file to control the location of a plaintext read following a seek-from-end. age now returns an error if the last chunk is invalid.
    • rage was not affected by this security issue, as it does not use Seek.
    • rage-mount may have been affected; it does not use SeekFrom::End directly, but the tar or zip crates might do so.

Added

  • Plugin support, enabled by the plugin feature flag:
    • age::plugin::{Identity, Recipient} structs for parsing plugin recipients and identities from strings.
    • age::plugin::RecipientPluginV1, which implements age::Recipient and runs the V1 recipient plugin protocol.
    • age::plugin::IdentityPluginV1, which implements age::Identity and runs the V1 identity plugin protocol.
  • The web-sys feature flag, which enables calculating the work factor for passphrase encryption with the Performance timer via the web-sys crate, when compiling for a WebAssembly target such as wasm32-unknown-unknown. This feature is ignored for the wasm32-wasi target, which supports std::time::SystemTime.
  • age::Callbacks::request_public_string to request non-private input from the user (which will not trigger any OS-level passphrase-style prompt, unlike Callbacks::request_passphrase).

Changed

  • MSRV is now 1.47.0.
  • age::cli_common::file_io::OutputWriter::File will now overwrite the file if it exists, instead of returning an error. This makes it consistent with age::cli_common::file_io::OutputWriter::Stdout, as well as most UNIX tools.
  • Files encrypted with this version of age might not decrypt with previous beta versions, due to changes in how stanza bodies are canonically encoded. This should only affect a small fraction of files (if grease that triggers the change is added, which has a 3% chance per file).
  • age::decryptor::RecipientsDecryptor now takes impl Iterator<Item = &'a dyn Identity> in its decryption methods, to make decrypting multiple files with the same identities easier.
  • age::cli_common::file_io::OutputWriter::File now wraps a LazyFile struct (instead of wrapping std::io::File directly), which does not open the file until it is first written to.
  • age::decryptor::Callbacks has been moved to age::Callbacks, as it is no longer decryption-specific.

Fixed

  • age::cli_common::read_identities now allows either kind of line ending in SSH identity files.
  • Default en-US language strings are now always loaded, even if translations are not loaded by calling age::localizer().select(&requested_languages).
  • StreamReader::seek(SeekFrom::End(0)) now seeks to the correct position when the plaintext is an exact multiple of the chunk size.

age-plugin 0.1.0

Initial beta release!

age-core

Security

  • age_core::primitives::aead_decrypt now takes a size argument, checked against the plaintext length. This is to mitigate multi-key attacks, where a ciphertext can be crafted that decrypts successfully under multiple keys. Short ciphertexts can only target two keys, which has limited impact. See this commit message for more details.

Added

  • age_core::format::FILE_KEY_BYTES constant.
  • age_core::plugin module, which contains common backend logic used by both the age library (to implement client support for plugins) and the age-plugin library.

Changed

  • The stanza prefix -> and trailing newline are now formal parts of the age stanza; age_core::format::write::age_stanza now includes them in its output, and age_core::format::read::age_stanza expects them to be present.
  • Stanza bodies are now canonically serialized with a short (empty if necessary) last line. age_core::format::write::age_stanza outputs the new encoding, and age_core::format::read::age_stanza accepts only the new encoding. The new API age_core::format::read::legacy_age_stanza accepts either kind of stanza body encoding (the legacy minimal encoding, and the new explicit encoding).

v0.5.1

3 years ago

Fixed

  • Bumped dependencies to i18n-embed-fl 0.3 and i18n-embed 0.10.2 to fix a transient dependency breakage, that broke cargo install rage because cargo install ignores Cargo.lock.