Compare commits
2 commits
3e2f608e27
...
22e3ebed43
Author | SHA1 | Date | |
---|---|---|---|
22e3ebed43 | |||
5bb3ad2d0d |
2 changed files with 136 additions and 3 deletions
112
README.md
Normal file
112
README.md
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
# OmegaUpload
|
||||||
|
|
||||||
|
OmegaUpload is a zero-knowledge temporary file hosting service.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Uploading a file:
|
||||||
|
$ omegaupload-cli upload https://paste.example.com path/to/file
|
||||||
|
https://paste.example.com/PgRG8Hfrr9rR#I1FG2oejo2gSjB3Ym1mEmRfcN4X8GXc2pZtZeiSsWFo=
|
||||||
|
|
||||||
|
# Uploading a file with a password:
|
||||||
|
$ omegaupload-cli upload -p https://paste.example.com path/to/file
|
||||||
|
Please set the password for this paste:
|
||||||
|
https://paste.crabravers.club/862vhXVp3v9R#key:tbGxzHBNnXjS2eq89X9uvZKz_i8bvapLPEp8g0waQrc=!pw
|
||||||
|
|
||||||
|
# Downloading a file:
|
||||||
|
$ omegaupload-cli download https://paste.example.com/PgRG8Hfrr9rR#I1FG2oejo2gSjB3Ym1mEmRfcN4X8GXc2pZtZeiSsWFo=
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Server has zero knowledge of uploaded data when uploading through a supported
|
||||||
|
frontend (Direct, plaintext upload is possible but unsupported).
|
||||||
|
- Only metadata stored on server is expiration time. This is a strong guarantee.
|
||||||
|
- All cryptographic functions are performed on the client side and are done via
|
||||||
|
a single common library, to minimize risk of programming error.
|
||||||
|
- Modern crypto functions are used with recommended parameters:
|
||||||
|
XChaCha20Poly1305 for encryption and Argon2id for KDF.
|
||||||
|
- Customizable expiration times, from burn-after-read to 1 day.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
- `yarn` 1.22.17 or later (Earlier versions untested but likely to work)
|
||||||
|
- [`trunk`](https://trunkrs.dev/)
|
||||||
|
- Cargo, with support for the latest Rust version
|
||||||
|
- _(Optional)_ zstd, for zipping up the file for distribution
|
||||||
|
|
||||||
|
First, run `git submodule update --init --recursive`.
|
||||||
|
|
||||||
|
Then, run `./bin/build.sh` for a `dist.tar.zst` to be generated, where you can
|
||||||
|
simply extract that folder and run the binary provided. The server will listen
|
||||||
|
on port `8080`.
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
For development, building is as simple as `cargo build`. Note that you may need
|
||||||
|
to run `trunk build` first before building the server. To run the server (even
|
||||||
|
for testing) requires uploading `dist.tar.zst` to a remote server.
|
||||||
|
|
||||||
|
## Why OmegaUpload?
|
||||||
|
|
||||||
|
OmegaUpload's primary benefit is that the frontends use a unified common library
|
||||||
|
utilizing XChaCha20Poly1305 to encrypt and decrypt files.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
The primary goal was to provide a unified library across both a CLI tool and
|
||||||
|
through the web frontend to minimize risk of compromise. As a result, the CLI
|
||||||
|
tool and the web frontend both utilize a Rust library whose crypto module
|
||||||
|
exposes two functions to encrypt and decrypt that only accept a message and
|
||||||
|
necessarily key material or return only necessary key material. This small API
|
||||||
|
effectively makes it impossible to have differences between the frontend, and
|
||||||
|
ensures that the attack surface is limited to these functions.
|
||||||
|
|
||||||
|
#### Password KDF
|
||||||
|
|
||||||
|
If a password is provided at encryption time, argon2 is used as a key derivation
|
||||||
|
function. Specifically, the library meets or exceeds OWASP recommended
|
||||||
|
parameters:
|
||||||
|
- Argon2id is used.
|
||||||
|
- Algorithm version is `0x13`.
|
||||||
|
- Parameters are `m = 15MiB`, `t = 2`, `p = 2`.
|
||||||
|
|
||||||
|
Additionally, a salt size of 16 bytes are used.
|
||||||
|
|
||||||
|
#### Blob Encryption
|
||||||
|
|
||||||
|
XChaCha20Poly1305 was used as the encryption method as it is becoming the
|
||||||
|
mainstream recommended method for encrypting messages. This was chosen over AES
|
||||||
|
primarily due to its strength in related-key attacks, as well as its widespread
|
||||||
|
recognition and usage in WireGuard, Quic, and TLS.
|
||||||
|
|
||||||
|
As this crate uses `XChaCha20`, a 24 byte nonce and a 32 bytes key are used.
|
||||||
|
|
||||||
|
#### Secrecy
|
||||||
|
|
||||||
|
Encryption and decryption functions offered by the common crate only accept or
|
||||||
|
return key material that will be properly zeroed on destruction. This is
|
||||||
|
enforced by the `secrecy` crate, which, on top of offering type wrappers that
|
||||||
|
zero the memory on drop, provide an easy way to audit when secrets are exposed.
|
||||||
|
|
||||||
|
This also means that to use these two functions necessarily requires the caller
|
||||||
|
to enclose key material in the wrapped type first, reducing possibility for key
|
||||||
|
material to remain in memory.
|
||||||
|
|
||||||
|
#### Memory Safety
|
||||||
|
|
||||||
|
Rust eliminates an entire class of memory-related bugs, and any `unsafe` block
|
||||||
|
is documented with a safety comment. This allows for easy auditing of memory
|
||||||
|
suspect code, and permits
|
||||||
|
|
||||||
|
## Why not OmegaUpload?
|
||||||
|
|
||||||
|
There are a few reasons to not use OmegaUpload:
|
||||||
|
- Limited to 3GB uploads—this is a soft limit of RocksDB.
|
||||||
|
- Cannot download files larger than 512 MiB through the web frontend—this
|
||||||
|
is a technical limitation of the current web frontend not using a web worker
|
||||||
|
in addition to the fact that browsers are not optimized for XChaCha20.
|
||||||
|
- Right now, you must upload via the CLI tool.
|
||||||
|
- The frontend uses WASM, which is a novel attack surface.
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use argon2::Argon2;
|
use argon2::{Argon2, ParamsBuilder};
|
||||||
use chacha20poly1305::aead::generic_array::sequence::GenericSequence;
|
use chacha20poly1305::aead::generic_array::sequence::GenericSequence;
|
||||||
use chacha20poly1305::aead::generic_array::GenericArray;
|
use chacha20poly1305::aead::generic_array::GenericArray;
|
||||||
use chacha20poly1305::aead::{AeadInPlace, NewAead};
|
use chacha20poly1305::aead::{AeadInPlace, NewAead};
|
||||||
|
@ -152,7 +152,7 @@ pub fn open_in_place(
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let pw_key = if let Some(password) = password {
|
let pw_key = if let Some(password) = password {
|
||||||
let salt_buf = data.split_off(data.len() - Salt::SIZE);
|
let salt_buf = data.split_off(data.len() - Salt::SIZE);
|
||||||
let argon = Argon2::default();
|
let argon = get_argon2();
|
||||||
let mut pw_key = Key::default();
|
let mut pw_key = Key::default();
|
||||||
argon
|
argon
|
||||||
.hash_password_into(password.expose_secret(), &salt_buf, &mut pw_key)
|
.hash_password_into(password.expose_secret(), &salt_buf, &mut pw_key)
|
||||||
|
@ -255,13 +255,34 @@ impl AsRef<[u8]> for Salt {
|
||||||
/// Hashes an input to output a usable key.
|
/// Hashes an input to output a usable key.
|
||||||
fn kdf(password: &SecretVec<u8>) -> Result<(Secret<Key>, Salt), argon2::Error> {
|
fn kdf(password: &SecretVec<u8>) -> Result<(Secret<Key>, Salt), argon2::Error> {
|
||||||
let salt = Salt::random();
|
let salt = Salt::random();
|
||||||
let hasher = Argon2::default();
|
let hasher = get_argon2();
|
||||||
let mut key = Key::default();
|
let mut key = Key::default();
|
||||||
hasher.hash_password_into(password.expose_secret().as_ref(), salt.as_ref(), &mut key)?;
|
hasher.hash_password_into(password.expose_secret().as_ref(), salt.as_ref(), &mut key)?;
|
||||||
|
|
||||||
Ok((Secret::new(key), salt))
|
Ok((Secret::new(key), salt))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns Argon2id configured as follows:
|
||||||
|
/// - 15MiB of memory (`m`),
|
||||||
|
/// - an iteration count of 2 (`t`),
|
||||||
|
/// - and 2 degrees of parallelism (`p`).
|
||||||
|
///
|
||||||
|
/// This follows the [minimum recommended parameters suggested by OWASP][rec].
|
||||||
|
///
|
||||||
|
/// [rec]: https://link.eddie.sh/vaQ6a.
|
||||||
|
fn get_argon2() -> Argon2<'static> {
|
||||||
|
let mut params = ParamsBuilder::new();
|
||||||
|
params
|
||||||
|
.m_cost(15 * 1024) // 15 MiB
|
||||||
|
.expect("Hard coded params to work")
|
||||||
|
.t_cost(2)
|
||||||
|
.expect("Hard coded params to work")
|
||||||
|
.p_cost(2)
|
||||||
|
.expect("Hard coded params to work");
|
||||||
|
let params = params.params().expect("Hard coded params to work");
|
||||||
|
Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params)
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetches a cryptographically secure random number generator. This indirection
|
/// Fetches a cryptographically secure random number generator. This indirection
|
||||||
/// is used for better auditing the quality of rng. Notably, this function
|
/// is used for better auditing the quality of rng. Notably, this function
|
||||||
/// returns a `Rng` with the `CryptoRng` marker trait, preventing
|
/// returns a `Rng` with the `CryptoRng` marker trait, preventing
|
||||||
|
|
Loading…
Reference in a new issue