Git Product home page Git Product logo

str4d / rage Goto Github PK

View Code? Open in Web Editor NEW
2.4K 32.0 92.0 3.29 MB

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

Home Page: https://age-encryption.org/v1

License: Apache License 2.0

Rust 81.45% Ruby 0.29% Fluent 18.27%
cli encryption zero-configuration secure-by-default unix-philosophy curve25519 scrypt rust rust-library yubikey

rage's Introduction

The age logo, an wireframe of St. Peters dome in Rome, with the text: age, file encryption

rage: Rust implementation of age

rage is a simple, modern, and secure file encryption tool, using the age format. It features small explicit keys, no config options, and UNIX-style composability.

The format specification is at age-encryption.org/v1. age was designed by @Benjojo12 and @FiloSottile.

The reference interoperable Go implementation is available at filippo.io/age.

Hardware PIV tokens such as YubiKeys are supported through the age-plugin-yubikey plugin.

For more plugins, implementations, tools, and integrations, check out the awesome age list.

Installation

Environment CLI command
Cargo (Rust 1.65+) cargo install rage
Homebrew (macOS or Linux) brew tap str4d.xyz/rage https://str4d.xyz/rage
brew install rage
Alpine Linux (edge) apk add rage
Arch Linux pacman -S rage-encryption
Debian Debian packages
NixOS Add to config: environment.systemPackages = [ pkgs.rage ];
Or run nix-env -i rage
openSUSE Tumbleweed zypper install rage-encryption
Ubuntu 20.04+ Debian packages
FreeBSD pkg install rage-encryption
Scoop (Windows) scoop bucket add main
scoop install main/rage

On Windows, Linux, and macOS, you can use the pre-built binaries.

Help from new packagers is very welcome. Please use the package name rage; the fallback package name rage-encryption should be used only when there are unavoidable name conflicts in package systems that use global namespaces. Do not rename any binaries; instead use your package system's conflicting package mechanism to prevent installation of both packages at once.

Usage

Usage: rage [--encrypt] (-r RECIPIENT | -R PATH)... [-i IDENTITY] [-a] [-o OUTPUT] [INPUT]
       rage [--encrypt] --passphrase [-a] [-o OUTPUT] [INPUT]
       rage --decrypt [-i IDENTITY] [-o OUTPUT] [INPUT]

Arguments:
  [INPUT]  Path to a file to read from.

Options:
  -h, --help                    Print this help message and exit.
  -V, --version                 Print version info and exit.
  -e, --encrypt                 Encrypt the input (the default).
  -d, --decrypt                 Decrypt the input.
  -p, --passphrase              Encrypt with a passphrase instead of recipients.
      --max-work-factor <WF>    Maximum work factor to allow for passphrase decryption.
  -a, --armor                   Encrypt to a PEM encoded format.
  -r, --recipient <RECIPIENT>   Encrypt to the specified RECIPIENT. May be repeated.
  -R, --recipients-file <PATH>  Encrypt to the recipients listed at PATH. May be repeated.
  -i, --identity <IDENTITY>     Use the identity file at IDENTITY. May be repeated.
  -j <PLUGIN-NAME>              Use age-plugin-PLUGIN-NAME in its default mode as an identity.
  -o, --output <OUTPUT>         Write the result to the file at path OUTPUT.

INPUT defaults to standard input, and OUTPUT defaults to standard output.
If OUTPUT exists, it will be overwritten.

RECIPIENT can be:
- An age public key, as generated by rage-keygen ("age1...").
- An SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...").

PATH is a path to a file containing age recipients, one per line
(ignoring "#" prefixed comments and empty lines). "-" may be used to
read recipients from standard input.

IDENTITY is a path to a file with age identities, one per line
(ignoring "#" prefixed comments and empty lines), or to an SSH key file.
Passphrase-encrypted age identity files can be used as identity files.
Multiple identities may be provided, and any unused ones will be ignored.
"-" may be used to read identities from standard input.

Multiple recipients

Files can be encrypted to multiple recipients by repeating -r/--recipient. Every recipient will be able to decrypt the file.

$ rage -o example.png.age -r age1uvscypafkkxt6u2gkguxet62cenfmnpc0smzzlyun0lzszfatawq4kvf2u \
    -r age1ex4ty8ppg02555at009uwu5vlk5686k3f23e7mac9z093uvzfp8sxr5jum example.png

Recipient files

Multiple recipients can also be listed one per line in one or more files passed with the -R/--recipients-file flag.

$ cat recipients.txt
# Alice
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# Bob
age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg
$ rage -R recipients.txt example.jpg > example.jpg.age

If the argument to -R (or -i) is -, the file is read from standard input.

Passphrases

Files can be encrypted with a passphrase by using -p/--passphrase. By default rage will automatically generate a secure passphrase.

$ rage -p -o example.png.age example.png
Type passphrase (leave empty to autogenerate a secure one): [hidden]
Using an autogenerated passphrase:
    kiwi-general-undo-bubble-dwarf-dizzy-fame-side-sunset-sibling
$ rage -d example.png.age >example.png
Type passphrase: [hidden]

If a binary named pinentry is available in $PATH, it will be used to ask the user for a passphrase. The PINENTRY_PROGRAM environment variable can be used to set the binary name or path to use. If a pinentry binary is not available, or PINENTRY_PROGRAM is set to the empty string, rage will fall back to the CLI instead.

Passphrase-protected identity files

If an identity file passed to -i/--identity is a passphrase-encrypted age file, it will be automatically decrypted.

$ rage -p -o key.age <(rage-keygen)
Public key: age1pymw5hyr39qyuc950tget63aq8vfd52dclj8x7xhm08g6ad86dkserumnz
Type passphrase (leave empty to autogenerate a secure one): [hidden]
Using an autogenerated passphrase:
    flash-bean-celery-network-curious-flower-salt-amateur-fence-giant
$ rage -r age1pymw5hyr39qyuc950tget63aq8vfd52dclj8x7xhm08g6ad86dkserumnz secrets.txt > secrets.txt.age
$ rage -d -i key.age secrets.txt.age > secrets.txt
Type passphrase: [hidden]

Passphrase-protected identity files are not necessary for most use cases, where access to the encrypted identity file implies access to the whole system. However, they can be useful if the identity file is stored remotely.

SSH keys

As a convenience feature, rage also supports encrypting to ssh-rsa and ssh-ed25519 SSH public keys, and decrypting with the respective private key file. (ssh-agent is not supported.)

$ rage -R ~/.ssh/id_ed25519.pub example.png > example.png.age
$ rage -d -i ~/.ssh/id_ed25519 example.png.age > example.png

Note that SSH key support employs more complex cryptography, and embeds a public key tag in the encrypted file, making it possible to track files that are encrypted to a specific public key.

Feature flags

When building with Cargo, you can configure rage using --no-default-features and --features comma,separated,flags to enable or disable the following feature flags:

  • mount enables the rage-mount tool, which can mount age-encrypted TAR or ZIP archives as read-only. It is currently only usable on Unix systems, as it relies on libfuse.

  • ssh (enabled by default) enables support for reusing existing SSH key files for age encryption.

  • unstable enables in-development functionality. Anything behind this feature flag has no stability or interoperability guarantees.

Rust Library

Applications wishing to use rage as a library should use the age crate, which rage is built on top of.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

rage's People

Contributors

alexgartrell avatar bonedaddy avatar buriedintheground avatar dconnolly avatar dependabot[bot] avatar duesee avatar dwhjames avatar ehaupt avatar filosottile avatar folliehiyuki avatar gibbz00 avatar hybras avatar jatoben avatar jfaust avatar kanru avatar macil avatar mdlayher avatar mutlusun avatar nickzana avatar ovnanova avatar pacu avatar rex4539 avatar romanz avatar ryantm avatar sjmurdoch avatar str4d avatar therealyingtong avatar thibmeu avatar wesleyac avatar woodruffw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rage's Issues

Cannot read age keys in CRLF files

Environment

  • OS: Windows 10 1909
  • rage version: 0.3.1

What were you trying to do

Decrypt a file encrypted with rage using a rage-keygen generated key.

What happened

rage says the secret key file is invalid

image

Rage refuses to write to stdout even when armor is enabled

PS C:\Users\Alice> rage -r age1wssqv0rf9ctp5w7j3dfmngurhe2xrj5869rv9x8a0gex5r0f6ejqy0prkw -a .\Desktop\age.txt
Error: not printing to stdout

[ Did rage not do what you expected? Could an error be more useful? ]
[ Tell us: https://github.com/str4d/rage/issues/new/choose          ]

Decide how to handle key read-write cycle

The X25519 function allows any 32-byte string as a secret key, and clamps it as a scalar on use. From 712c025 the library stores keys internally in clamped form (rage -g returns clamped keys but rage -d accepts both clamped and unclamped). This means that reading and then writing a key may result in a different encoding. I do not know if this is likely to trip anyone up, and in any case I cannot think of any reason for anyone to be performing this operation. That being said, it would be nice to document this behaviour somewhere, and/or discourage or prevent the read/write cycle.

Async armor writing

ArmoredReader implements AsyncRead, which can be used with the async decryption API. We should also implement AsyncWrite for ArmoredWriter, to enable armor writing for the async encryption API.

TTY detection and handling

  • Prevent encrypted output from being sent to stdout if it is bound to a TTY.
  • Prevent long or unprintable decrypted output from being sent to stdout if it is bound to a TTY.
  • Require a TTY when using the --passphrase / -p flag.
  • Turn off TTY echo while reading the passphrase (where possible).

Invalid ssh-rsa recipient

Environment

  • OS: NixOS
  • rage version: 0.4.0

What were you trying to do

Encrypt something for an ssh-rsa recipient.

What happened

$ echo "hi" | rage -r 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCgsdlz/hsLukkL1GdTENASfkGNtLoY6aABDzcXnis11LKCCVpm4JuwJzcdMpTqkwRFw+RHF5GGVAp7t6LMDBSwuRGuowVEnxw9zVgA5z9cWAGf9UXVxixBTB3ersYQBpha3kHML47y7iYYmt8h6C7QsyEzlio2xDXuif+AVN3XysQD6SenwA7agSCiKkJnuAfDZr9W0fuZuIPiTAj6XTQY/hA0oBgufIfJKmGcyNLhaz3FcdADngbwhDmsBCwBBVT9raEGKogv4v+BFGVcHrUDw74zqpg9vVhvX8YXCuWAnsBJj7+GekwDL0PmFjUZN/TVQEigFX2/609XI/mqxNJMSa1D9m16WC1kcuq2zyUGe/5Cp3eFiKR/cV4B7hHJ8cU64pO5sPokUzE2w7GkTyZJYhogDBj535Y5LwE2KTOssrzWMGDFUUiLCEu8Ty1OgKE5sfyv+vOo5vC2kkbs+q7b22h+9O7yPtpsnxpaKj/09QjMMlYx7reTDjQCRRkjppVLZxtHdE+gsvwcfDTIP48h5Lk7Jdf481O9I18eMWpZv1Eg9J/oQ9GgBowZCkD472a2mkCRSE0fY6u7o3oqW7s+/uYmqfaJjLpvvkwqwcrH4UCV03wyNuDDT3KVRtjjNf+mjiGJOyV1hpgXpbmcR2xb+Q1m1zTAspXr8VwQtY01Ww== ryantm-2018-09-27' -a
Error: Invalid recipient 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCgsdlz/hsLukkL1GdTENASfkGNtLoY6aABDzcXnis11LKCCVpm4JuwJzcdMpTqkwRFw+RHF5GGVAp7t6LMDBSwuRGuowVEnxw9zVgA5z9cWAGf9UXVxixBTB3ersYQBpha3kHML47y7iYYmt8h6C7QsyEzlio2xDXuif+AVN3XysQD6SenwA7agSCiKkJnuAfDZr9W0fuZuIPiTAj6XTQY/hA0oBgufIfJKmGcyNLhaz3FcdADngbwhDmsBCwBBVT9raEGKogv4v+BFGVcHrUDw74zqpg9vVhvX8YXCuWAnsBJj7+GekwDL0PmFjUZN/TVQEigFX2/609XI/mqxNJMSa1D9m16WC1kcuq2zyUGe/5Cp3eFiKR/cV4B7hHJ8cU64pO5sPokUzE2w7GkTyZJYhogDBj535Y5LwE2KTOssrzWMGDFUUiLCEu8Ty1OgKE5sfyv+vOo5vC2kkbs+q7b22h+9O7yPtpsnxpaKj/09QjMMlYx7reTDjQCRRkjppVLZxtHdE+gsvwcfDTIP48h5Lk7Jdf481O9I18eMWpZv1Eg9J/oQ9GgBowZCkD472a2mkCRSE0fY6u7o3oqW7s+/uYmqfaJjLpvvkwqwcrH4UCV03wyNuDDT3KVRtjjNf+mjiGJOyV1hpgXpbmcR2xb+Q1m1zTAspXr8VwQtY01Ww== ryantm-2018-09-27'

[ Did rage not do what you expected? Could an error be more useful? ]
[ Tell us: https://str4d.xyz/rage/report                            ]

$ echo "hi" | age -r 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCgsdlz/hsLukkL1GdTENASfkGNtLoY6aABDzcXnis11LKCCVpm4JuwJzcdMpTqkwRFw+RHF5GGVAp7t6LMDBSwuRGuowVEnxw9zVgA5z9cWAGf9UXVxixBTB3ersYQBpha3kHML47y7iYYmt8h6C7QsyEzlio2xDXuif+AVN3XysQD6SenwA7agSCiKkJnuAfDZr9W0fuZuIPiTAj6XTQY/hA0oBgufIfJKmGcyNLhaz3FcdADngbwhDmsBCwBBVT9raEGKogv4v+BFGVcHrUDw74zqpg9vVhvX8YXCuWAnsBJj7+GekwDL0PmFjUZN/TVQEigFX2/609XI/mqxNJMSa1D9m16WC1kcuq2zyUGe/5Cp3eFiKR/cV4B7hHJ8cU64pO5sPokUzE2w7GkTyZJYhogDBj535Y5LwE2KTOssrzWMGDFUUiLCEu8Ty1OgKE5sfyv+vOo5vC2kkbs+q7b22h+9O7yPtpsnxpaKj/09QjMMlYx7reTDjQCRRkjppVLZxtHdE+gsvwcfDTIP48h5Lk7Jdf481O9I18eMWpZv1Eg9J/oQ9GgBowZCkD472a2mkCRSE0fY6u7o3oqW7s+/uYmqfaJjLpvvkwqwcrH4UCV03wyNuDDT3KVRtjjNf+mjiGJOyV1hpgXpbmcR2xb+Q1m1zTAspXr8VwQtY01Ww== ryantm-2018-09-27' -a
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1yc2EgN25CQS9BCkpDNHVzUkh5
ZEtHcUN1dURUR0xWSXMwbmE5cVZzdUpSQk94VVYranBxMGpkWUNWWlZ6T3BrdWdD
SVlnbjQ1Z3EKNkNsMlAzT0Jnc2NWc29FNTJ1ODZKaHZlUmVDT2ZQQzZzeHpzSXQ5
Mkp4WUhuYnBzaVFnZVFRUHdzLzE4RytMTQpKQklpcnVYaGNtcFFhdUtSaVdSRzlX
d0RLRXVCSGdIWk81Q1Q1RWFnNjJzaC9QQUl1T3RyZzVPSWp2dHYzMUFNCk1NRFFJ
QUZobGhsVTcrL2MwRXpHUkVIWUJqbXR0YjlvV1l3NWFLREFrMmNSWUFRYnZmZ0J2
REdWK0VhRXR2TGsKNkx1eitQUWRwL2tPajQ4SnFIYUIya2Z2Yk5iSU5oNHZMbW54
anc2dlA4RERtTEQxTkRua0Y0MnhvTXV0bHRYeAo3aGhlOWU1c1FzZ1NKOGorYmFF
SEFrM2ZSMlRramNZaWFsbURMZnQ4V1lucE95UTBMZy9HKzM2bzNXQ3IwaDE5ClpI
UGMrTkxrN3VCNFlLYWh5c29kdzltT1pLWFBnOWxnd0g4M1ZFdnYwOGhQTGhOZzRs
ZHhnRWpGY3U1M3JRam4KTG9JbWhNT2k0aU4vYmkvUVpEamUrT2EyM1VPMlFSZWVX
OHFjMkRmSFBzdVNIR3JlMTB2KzFKZG5MQm5RVVM4VAo2ZGdYaU9FdFJpVVFLdzg3
WUpOOHVsTUtFdklNZXJjbThSYUQ1clFmVUZ2S0dRelBxdElMdzZzQ1dpcFk0MU1j
CmNnUGFxckNWR2l6N2xMNEw1VGUzeUZlL2tiU0RXaG9PU2tBa1RNK05nMVYyUWNx
MzRXUWRveGhQaXRJU0FubmkKRUdSWUYrTFJobklGSDV1QzFMSXM1RU52OHlZdnoz
ZkFCSTJ6YkZYY0FZMAotLS0gT3JWeXBtOFRaa1NOdCsrbFhUVC84KzV1ZnhKNCtI
NTljR3hYVzZIbEtFNAqkiHSiCZS8bkXDO5gn9EVT0XITJwLnh+puQGWK230l+mhX
MA==
-----END AGE ENCRYPTED FILE-----

Fuzzer: Crash during decryption on reading misformed UTF-8 in header

Yay fuzzing!

The problem line was introduced when I migrated to the new armor format. The bug is here:

if !is_armored {
// The line we read was likely a header line
if buf.len() < self.line_buf.len() {
let remaining = self.line_buf.split_off(buf.len());

The artifact that triggers it is (encoded in Base64 so it's pasteable):

LS0tLS1CRUdJTiBBR0UgRU5Bwq1ZUFRFRCBGSUxFLS0tLS0tLUItLS0tLUJFR0lOIEFHRSBFTkNSWVBURUQgRklMRS0tLS0oRUdJTiBBR0UgRU5SQ1lQVEVEIEZJTEUtLS0tLSBGSUxFLS0tLS0AABIAAAoAAAAAAA==

The crash:

thread 'main' panicked at 'assertion failed: self.is_char_boundary(at)', src/liballoc/string.rs:1467:9
stack backtrace:
   0: backtrace::backtrace::libunwind::trace
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/libunwind.rs:88
   1: backtrace::backtrace::trace_unsynchronized
             at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/backtrace/mod.rs:66
   2: std::sys_common::backtrace::_print_fmt
             at src/libstd/sys_common/backtrace.rs:77
   3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt   
             at src/libstd/sys_common/backtrace.rs:61
   4: core::fmt::write
             at src/libcore/fmt/mod.rs:1028
   5: std::io::Write::write_fmt
             at src/libstd/io/mod.rs:1412
   6: std::sys_common::backtrace::_print
             at src/libstd/sys_common/backtrace.rs:65
   7: std::sys_common::backtrace::print
             at src/libstd/sys_common/backtrace.rs:50
   8: std::panicking::default_hook::{{closure}}
             at src/libstd/panicking.rs:188
   9: std::panicking::default_hook
             at src/libstd/panicking.rs:205
  10: std::panicking::rust_panic_with_hook
             at src/libstd/panicking.rs:464
  11: std::panicking::continue_panic_fmt
             at src/libstd/panicking.rs:373
  12: rust_begin_unwind
             at src/libstd/panicking.rs:302
  13: core::panicking::panic_fmt
             at src/libcore/panicking.rs:139
  14: core::panicking::panic
             at src/libcore/panicking.rs:70
  15: alloc::string::String::split_off
             at ./<::core::macros::panic macros>:5
  16: <age::primitives::armor::ArmoredReader<R> as std::io::Read>::read
             at ./age/src/primitives/armor.rs:156
  17: std::io::Read::read_exact
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libstd/io/mod.rs:768  
  18: std::io::impls::<impl std::io::Read for &mut R>::read_exact
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libstd/io/impls.rs:39 
  19: age::format::Header::read
             at ./age/src/format.rs:118
  20: age::protocol::Decryptor::trial_decrypt
             at ./age/src/protocol.rs:129
  21: rage::decrypt
             at rage/src/bin/rage/main.rs:303
  22: rage::main
             at rage/src/bin/rage/main.rs:332
  23: std::rt::lang_start::{{closure}}
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libstd/rt.rs:61       
  24: std::rt::lang_start_internal::{{closure}}
             at src/libstd/rt.rs:48
  25: std::panicking::try::do_call
             at src/libstd/panicking.rs:287
  26: __rust_maybe_catch_panic
             at src/libpanic_unwind/lib.rs:78
  27: std::panicking::try
             at src/libstd/panicking.rs:265
  28: std::panic::catch_unwind
             at src/libstd/panic.rs:396
  29: std::rt::lang_start_internal
             at src/libstd/rt.rs:47
  30: std::rt::lang_start
             at /rustc/73528e339aae0f17a15ffa49a8ac608f50c6cf14/src/libstd/rt.rs:61       
  31: main
  32: __libc_start_main
  33: _start
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

UX: age doesn't require -p for decryption of passphrase

What were you trying to do

Decrypt passphrase-encrypted file, rage -d test.rage > test with rage 0.3.1.

What happened

Error: Missing identities.
Did you forget to specify -i/--identity?
You can also store default identities in this file:
/home/user/.config/age/keys.txt

rage -d -p test.rage > test is successful.

From age:

Passphrase protected files are automatically detected at decrypt time.

age -d test.age > test is successful. -p not required.

Make default max scrypt work configurable

Currently, rage estimates the target work required for around 1 second of effort on the current machine, and then allows a work factor up to 24 higher, or around 16 seconds of effort. This is reasonable for interoperability within rage users, but is a problem for interoperability with the reference Golang implementation:

  • The SHA-256 implementation in the sha2 crate is pure-Rust, and much slower than the assembly-optimized one available in Golang.
  • age picks a work factor of 18 as the default, which is indeed about 1 second of effort on my laptop. But rage picks a work factor of 12 for the same effort, and thus its default max work factor is 16.
  • The UX effect is that age can decrypt rage-encrypted messages, but rage rejects age-encrypted messages with "Excessive work parameter for passphrase".

To address this, the default max work factor should be configurable. We should provide error-message feedback to the user when the work exceeds the currently-configured max work factor, letting them know what the max would need to be to decrypt the message, and we can use the work estimator to inform them how long it would likely take.

UX: Unusable for multiple (asynchronous) file encryption / decryption

Hello! I'm using the rust crate (0.4.0) in order to use age in my android application.
I'm pretty new to rust and absolutely ignorant in cryptography, so please pardon me if what I'm going to say sounds really dumb.

Use case: the user needs to access an repository of encrypted files. I'm discarding a (hierarchy of) encrypted archives because:

  • If the user wants to access only a specific file, he's obliged to download / access the whole archive and decrypt it entirely;
  • If he wants to update an archive, he also has to encrypt the whole thing and upload / store it again, instead of adding only the new file.

I couldn't figure out how to properly perform asynchronous encryption / decryption on multiple files (using the same passphrase) without making the memory usage spike: encryptor.wrap_output(...) takes more than 200MB of ram, so allocating a StreamWriter<W> for each file is unfeasible as the ram usage could (and will) spike to hundreds of megabytes (and the app will be eventually killed by the os).

On the other side, doing it synchronously, the whole thing works, but it takes a lot of time (of course), and it wouldn't be optimized as well, because both the SecretString and the StreamWriter<W> needs to be allocated / deallocated for every single file, and so there is some useless overhead.

So yeah that's all, I apologize if this makes no sense.

Refactor RecipientStanza

RecipientStanza started as an enum, and we parsed the header into the exact recipient types we knew about. This worked well when we also knew the exact set of supported recipient / identity types, but since the Identity trait was introduced in #117 we no longer have complete knowledge. We need to rework the parsing, wrapping, and unwrapping logic to be based around a common Stanza type, which individual recipient / identity types parse further.

Blocks the next release, because we made the current enum-based RecipientStanza public in #117.

cookie-factory dependency fails to compile with latest nightly

Environment

  • OS: macOS 10.15.4 (19E224g)
  • rage version: master
% git clone [email protected]:str4d/rage.git
Cloning into 'rage'...
remote: Enumerating objects: 180, done.
remote: Counting objects: 100% (180/180), done.
remote: Compressing objects: 100% (93/93), done.
remote: Total 2087 (delta 102), reused 142 (delta 80), pack-reused 1907
Receiving objects: 100% (2087/2087), 681.39 KiB | 1.96 MiB/s, done.
Resolving deltas: 100% (1268/1268), done.
% cd rage
% cargo build
   Compiling libc v0.2.66
   Compiling typenum v1.11.2
   Compiling cfg-if v0.1.10
   Compiling proc-macro2 v1.0.8
   Compiling byteorder v1.3.2
   Compiling unicode-xid v0.2.0
   Compiling syn v1.0.14
   Compiling memchr v2.3.0
   Compiling getrandom v0.1.14
   Compiling semver-parser v0.7.0
   Compiling lazy_static v1.4.0
   Compiling cc v1.0.50
   Compiling log v0.4.8
   Compiling arrayvec v0.4.12
   Compiling subtle v1.0.0
   Compiling opaque-debug v0.2.3
   Compiling ryu v1.0.2
   Compiling autocfg v1.0.0
   Compiling version_check v0.1.5
   Compiling spin v0.5.2
   Compiling subtle v2.2.2
   Compiling nodrop v0.1.14
   Compiling untrusted v0.7.0
   Compiling byte-tools v0.3.1
   Compiling ppv-lite86 v0.2.6
   Compiling regex-syntax v0.6.14
   Compiling static_assertions v0.3.4
   Compiling fake-simd v0.1.2
   Compiling remove_dir_all v0.5.2
   Compiling unicode-width v0.1.7
   Compiling quick-error v1.2.3
   Compiling base64 v0.11.0
   Compiling cookie-factory v0.3.0
   Compiling arrayref v0.3.6
   Compiling termcolor v1.1.0
   Compiling bech32 v0.7.2
   Compiling thread_local v1.0.1
   Compiling semver v0.9.0
error[E0415]: identifier `s` is bound more than once in this parameter list
   --> /Users/rex/.cargo/registry/src/github.com-1ecc6299db9ec823/cookie-factory-0.3.0/src/internal.rs:144:36
    |
144 |     fn skip(s: WriteContext<Self>, s: usize) -> GenResult<Self>
    |                                    ^ used as parameter more than once

error: aborting due to previous error

For more information about this error, try `rustc --explain E0415`.
error: could not compile `cookie-factory`.
warning: build failed, waiting for other jobs to finish...
error: build failed

spin is no longer actively maintained (RUSTSEC-2019-0031)

Hey,

Cargo Audit found that the spin crate is no longer maintained:

# cargo audit
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 59 security advisories (from /home/duesee/.cargo/advisory-db)
    Scanning Cargo.lock for vulnerabilities (187 crate dependencies)
error: Vulnerable crates found!

ID:	 RUSTSEC-2019-0031
Crate:	 spin
Version: 0.5.2
Date:	 2019-11-21
URL:	 https://github.com/mvdnes/spin-rs/commit/7516c80
Title:	 spin is no longer actively maintained
Solution: upgrade to: 

error: 1 vulnerability found!

Maybe cargo audit could be integrated as a CI test via GitHub Action, Travis, etc.?

Re-enable ~/.config/age/keys.txt

I accidentally disabled it by requiring an -i/--identity parameter while improving error messages. This error message should instead only be returned if there are no -i/--identity parameters and no keys.txt config file.

Yubikey support

Some people store their RSA SSH private keys on a Yubikey, and it would be great if we could decrypt messages encrypted to these keys (which could happen quite easily if a message is encrypted to github:USERNAME).

This specifically requires figuring out whether we can access RSA-OAEP decryption on a Yubikey. I have heard folk tales that it might be possible.

GitHub keys file contains non-recipient data

Environment

  • OS: NixOS
  • rage version: 0.4.0

What were you trying to do

Encrypt based on my GitHub keys URL.

What happened

$ echo "hi" | rage -r 'https://github.com/ryantm.keys' -a
[ERROR rage] Invalid("invalid recipient key")
Error: recipients file https://github.com/ryantm.keys contains non-recipient data

[ Did rage not do what you expected? Could an error be more useful? ]
[ Tell us: https://str4d.xyz/rage/report                            ]

Periodically test the release process

The release action currently only runs when a tagged commit is pushed. We should instead run it on most (all?) commits, but only upload the result for a tagged commit. This would ensure that we catch regressions in the release process before the release PR.

Rework recipient lines and recipient parsing to be pluggable

Currently each supported recipient line and type is hard-coded and special-cased. While this is nice for e.g. building the enums, it makes it harder to extend with new recipient types, which is the single joint in the age format. In the interest of keeping that joint well oiled, we should rework the recipient line handling logic to be pluggable. Initially this would be an internal decision, but it should be usable via the library interface, and maybe later via binary plugins for the rage tool suit.

Related to #36.

Ignore unknown recipient types

Currently rage treats an encrypted file as invalid if an unknown recipient line is present. The age specification requires that we instead ignore unknown lines.

Blocked on:

  • Specifying the per-recipient format (FiloSottile/age#9).
  • Deciding how this interacts with "if an scrypt line is present, it is the only line"-type rules.

UX: Simplify brew installation

What were you trying to do

Install the Homebrew package.

What happened

I maintain a Brewfile that excludes transient dependencies for the software I want from the Homebrew repositories.
To install from a file like this, you'd do something along the lines of cat Brewfile | xargs brew install.
However, because the Formula is embedded within this repository, brew install str4d/rage/rage prompts for GitHub credentials disrupting my workflow.
If you look at my Brewfile, jzelinskie/faq/faq and txn2/tap/kubefwd are taps that work without this prompt.
A nice side effect is that installation becomes one line rather than having an explicit tap command before install.

$ brew install str4d/rage/rage
==> Tapping str4d/rage
Cloning into '/usr/local/Homebrew/Library/Taps/str4d/homebrew-rage'...
Username for 'https://github.com':^C
$ brew install jzelinskie/faq/faq
==> Installing faq from jzelinskie/faq
==> Downloading https://github.com/jzelinskie/faq/releases/download/0.0.6/faq-darwin-amd64
Already downloaded: /Users/jzelinskie/Library/Caches/Homebrew/downloads/9b55fe03b1640a7d62374496e112f9072f5df703b4eef71c8792e3f6f491344a--faq-darwin-amd64
==> mv faq-darwin-amd64 faq
๐Ÿบ  /usr/local/Cellar/faq/0.0.6: 3 files, 12MB, built in 2 seconds

Thanks for the project and see you on Discord ๐Ÿค—

Decide on binary name

The crate and binary are both currently named age. I named this repo rage to indicate it was a Rust implementation (thanks, person in @FiloSottile's stream chat!), but I have the age crate name on crates.io (rage is some web-related crate).

I'm not very familiar with how interchangeable tool implementations usually perform collision-avoidance, but I assume it is usually by choosing separate binary names. This probably makes sense given that CLI argument handling is likely to not be exactly compatible between Rust and Go, but I'd like to get some feedback on this vs the UX road-bump of having a Rust binary crate that installs a binary with a slightly different name to the crate.

"Proper" PEM support for OpenSSH private keys

Currently we only read an OpenSSH key from a file if the opening tag is the first line. This is fine for generated keys in standalone files, but technically PEM allows for a key to be embedded in an arbitrary document, and the parser should just search for the opening tag.

Move CLI tools into the rage crate

The rage crate name has been very kindly transferred to this project by Awpteamoose! Now we can move to cleaner architecture where the age crate provides the library, and the rage crate depends on age and provides the CLI tools.

This issue can be closed once the repository has been reorganised into a Rust workspace with several inner crates:

  • age (the current crate, moved into a subdirectory)
  • rage (new crate with the binaries moved into it)
  • Future crates such as the YubiKey plugin.

As we are in beta, we don't need to worry too much about migrating CLI users from age to rage; we can just document this in the changelog and make a note in the README.

Piping armor'd output to echo on macOS fails with "Error: Broken pipe (os error 32)"

Environment

  • OS: macOS 10.14.6
  • rage version: HEAD

What were you trying to do

rage --passphrase input -a | echo

What happened

Error: Broken pipe (os error 32)

16:57:48 โ–ถ ./target/debug/rage --passphrase ./LICENSE-MIT -a | echo

Type passphrase (leave empty to autogenerate a secure one): [hidden]
Error: Broken pipe (os error 32)

[ Did rage not do what you expected? Could an error be more useful? ]
[ Tell us: https://github.com/str4d/rage/issues/new/choose          ]

[FEATURE] Please create homebrew package for OSX

While I'm personally fine compiling this with cargo I've noticed adoption tends to be much easier for folks on osx if you provide a homebrew package to give you something like brew install rage (which is both useful and hilarious as a command... ๐Ÿ˜œ )

Could we have one? (or I could take a shot at a formula for it though never done it myself.).

thanks!
Daryl.

Add support for pinentry interfaces

One of the nicer things that has resulted from gpg is a widely-deployed set of pinentry binaries, which provide a common way to show a modal requesting a PIN or passphrase input. If the binary is available, this would be a nice way to request PIN or passphrase input, which would also likely resolve #2 (currently trying to request a passphrase from TTY breaks if the input to age is from stdin. We would fall back to the current CLI-based passphrase entry if pinentry is unavailable.

The downside is that the pinentry binaries are backed by libassuan, which is a rather comprehensive IPC protocol for configuring and using the dialog prompt, which we'll need to write protocol handling for.

Rewrite ArmoredReader

I really don't like my first implementation ๐Ÿ˜

We should do this after we have a proper spec for ASCII-armored age messages.

Extract armoring from streaming

Currently StreamReader wraps ArmoredReader, and StreamWriter wraps ArmoredWriter. This is not nice from an encapsulation perspective (we'd like to make the STREAM implementation generic for other Rust libraries at some point), and prevents an API user from enforcing non-malleability of age-encrypted files in the type system.

We should extract armoring into a separate module. Library users can explicitly wrap their readers and writers before encryption or decryption, and the rage app will do this itself in order to maintain its ability to handle any input.

UX: Parse recipients as filenames last instead of first

Currently, rage tries parsing a -r/--recipient argument as a filename first. If that file exists, then age will read it as a list of recipients (one per line, ignoring comments and blank lines), returning an error on any non-recipient content. If the file does not exist, then it tries other formats (which are all string-prefixed). However, basically all the string-prefixed formats (excluding e.g. HTTPS URLs) are themselves valid filenames. While it is unlikely that a user would have filenames named after age keys, it is possible that e.g. the GitHub notation (currently github:username, might be changing to @username) could be a filename. The only way around this currently is to move or rename the file, which is not intuitive.

Instead, let's parse the recipient as a filename last. Then if a user really wants to open a filename that is also itself a recipient key, they can simply use an absolute path.

UX: set feature flags of rand for wasm target

What were you trying to do

compile to wasm

What happened

it works but I found it a bit difficult due to rand dep

We have to make sure that Os::Rand is not called in the browser cause it panics. This can be configured in rand 0.7.3 by using
rand = { version = "0.7.3", features = ["wasm-bindgen"] } however this is now deprecated in rust-random/rand#948 and I would need to do something along the same lines with getrandom now but the problem is basically the same.

This is basically a question of me not understanding how to best handle this in Cargo.toml and not sure if I need to do/ know something or if the proper way would be for this crate to make the solution easier.

As far as I understand, if I add rand as a dependency in my project explicitly (instead of it just being transitively required by age) I can specify feature flags and because both age and my crate use the same rand version, both use the one configured with my feature flag (my crate does not actually "use" rand itself, it just adds it as a dep such that it can set its features).

If however, for some reason, I would want to use a rand version incompatible with age, I could not use this solution and my create would use two rand versions at the same time (afaik this is supported by rust) (using one directly and another one as a dep of one age) and by doing this I would loose the ability to effectively set/ overwrite the feature flags of my transitive dependencies.

My suggestion is that I either don't understand how to handle this or that age might need to expose a feature flag which gets forwarded to rand.

(Performance is fine now that I compile in release mode btw. so your fix regarding os::Time was perfectly fine)

Aliases support

Both from the default system location, and specified with the --aliases flag.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.