master
Edward Shen 2021-10-31 00:57:52 -07:00
parent 24eff63a5e
commit b833f97c55
Signed by: edward
GPG Key ID: 19182661E818369F
7 changed files with 86 additions and 96 deletions

View File

@ -12,9 +12,6 @@ sed -i 's#stylesheet" href="/main#stylesheet" href="/static/main#g' dist/index.h
# Build server
cargo build --release --bin omegaupload-server
# index.html no longer needed, served statically by the upload server
rm dist/index.html
# Prepare assets for upload to webserver
mkdir -p dist/static
# Move everything that's not index.html into a `static` subdir

View File

@ -134,13 +134,14 @@ fn handle_download(mut url: ParsedUrl) -> Result<()> {
let mut data = res.bytes()?.as_ref().to_vec();
let mut password = None;
if url.needs_password {
let password = if url.needs_password {
// Only print prompt on interactive, else it messes with output
let maybe_password =
prompt_password_stderr("Please enter the password to access this paste: ")?;
password = Some(SecretVec::new(maybe_password.into_bytes()));
}
Some(SecretVec::new(maybe_password.into_bytes()))
} else {
None
};
open_in_place(&mut data, &url.decryption_key, password)?;

View File

@ -1,4 +1,3 @@
use std::fmt::Display;
use std::ops::{Deref, DerefMut};
use argon2::Argon2;
@ -11,33 +10,16 @@ use rand::{thread_rng, Rng};
use secrecy::{ExposeSecret, Secret, SecretVec, Zeroize};
use typenum::Unsigned;
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum Error {
ChaCha20Poly1305(chacha20poly1305::aead::Error),
Argon2(argon2::Error),
}
impl From<chacha20poly1305::aead::Error> for Error {
fn from(err: chacha20poly1305::aead::Error) -> Self {
Error::ChaCha20Poly1305(err)
}
}
impl From<argon2::Error> for Error {
fn from(err: argon2::Error) -> Self {
Error::Argon2(err)
}
}
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::ChaCha20Poly1305(_) => write!(f, "Decryption failed"),
Error::Argon2(_) => write!(f, "KDF failed"),
}
}
#[error("Invalid password.")]
Password,
#[error("Invalid secret key.")]
SecretKey,
#[error("An error occurred while trying to decrypt the blob.")]
Encryption,
#[error("An error occurred while trying to derive a secret key.")]
Kdf,
}
// This struct intentionally prevents implement Clone or Copy
@ -72,7 +54,7 @@ impl DerefMut for Key {
impl Zeroize for Key {
fn zeroize(&mut self) {
self.0.zeroize()
self.0.zeroize();
}
}
@ -92,7 +74,7 @@ impl Zeroize for Key {
///
/// Where:
/// - `C(message, key, nonce)` represents encrypting a provided message with
/// XChaCha20Poly1305.
/// `XChaCha20Poly1305`.
/// - `rng_key` represents a randomly generated key.
/// - `kdf(pw, salt)` represents a key derived from Argon2.
pub fn seal_in_place(
@ -101,14 +83,18 @@ pub fn seal_in_place(
) -> Result<Secret<Key>, Error> {
let (key, nonce) = gen_key_nonce();
let cipher = XChaCha20Poly1305::new(key.expose_secret());
cipher.encrypt_in_place(&nonce, &[], message)?;
cipher
.encrypt_in_place(&nonce, &[], message)
.map_err(|_| Error::Encryption)?;
let mut maybe_salt_string = None;
if let Some(password) = pw {
let (key, salt_string) = kdf(&password)?;
let (key, salt_string) = kdf(&password).map_err(|_| Error::Kdf)?;
maybe_salt_string = Some(salt_string);
let cipher = XChaCha20Poly1305::new(key.expose_secret());
cipher.encrypt_in_place(&nonce.increment(), &[], message)?;
cipher
.encrypt_in_place(&nonce.increment(), &[], message)
.map_err(|_| Error::Encryption)?;
}
message.extend_from_slice(nonce.as_slice());
@ -127,7 +113,9 @@ pub fn open_in_place(
let salt_buf = data.split_off(data.len() - Salt::SIZE);
let argon = Argon2::default();
let mut pw_key = Key::default();
argon.hash_password_into(password.expose_secret(), &salt_buf, &mut pw_key)?;
argon
.hash_password_into(password.expose_secret(), &salt_buf, &mut pw_key)
.map_err(|_| Error::Kdf)?;
Some(Secret::new(pw_key))
} else {
None
@ -139,11 +127,15 @@ pub fn open_in_place(
if let Some(key) = pw_key {
let cipher = XChaCha20Poly1305::new(key.expose_secret());
cipher.decrypt_in_place(&nonce.increment(), &[], data)?;
cipher
.decrypt_in_place(&nonce.increment(), &[], data)
.map_err(|_| Error::Password)?;
}
let cipher = XChaCha20Poly1305::new(key.expose_secret());
cipher.decrypt_in_place(&nonce, &[], data)?;
cipher
.decrypt_in_place(&nonce, &[], data)
.map_err(|_| Error::SecretKey)?;
Ok(())
}
@ -208,13 +200,13 @@ impl Nonce {
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
struct Salt([u8; Salt::SIZE]);
struct Salt([u8; Self::SIZE]);
impl Salt {
const SIZE: usize = argon2::password_hash::Salt::RECOMMENDED_LENGTH;
fn random() -> Self {
let mut salt = [0u8; Salt::SIZE];
let mut salt = [0_u8; Self::SIZE];
thread_rng().fill(&mut salt);
Self(salt)
}

View File

@ -41,9 +41,7 @@ impl From<&str> for PartialParsedUrl {
// Base64 has an interesting property that the length of an encoded text
// is always 4/3rds larger than the original data.
if !fragment.contains("key") {
let decryption_key = base64::decode(fragment)
.ok()
.and_then(|k| Key::new_secret(k));
let decryption_key = base64::decode(fragment).ok().and_then(Key::new_secret);
return Self {
decryption_key,
@ -66,7 +64,7 @@ impl From<&str> for PartialParsedUrl {
for (key, value) in args {
match (key, value) {
("key", Some(value)) => {
decryption_key = base64::decode(value).ok().and_then(|k| Key::new_secret(k));
decryption_key = base64::decode(value).ok().and_then(Key::new_secret);
}
("pw", _) => {
needs_password = true;

View File

@ -94,6 +94,8 @@ async fn main() -> Result<()> {
Ok(())
}
// See https://link.eddie.sh/5JHlD
#[allow(clippy::cognitive_complexity)]
fn set_up_expirations(db: &Arc<DB>) {
let mut corrupted = 0;
let mut expired = 0;
@ -161,15 +163,12 @@ fn set_up_expirations(db: &Arc<DB>) {
async fn handle_signals(mut signals: Signals, db: Arc<DB>) {
while let Some(signal) = signals.next().await {
match signal {
SIGUSR1 => {
let meta_cf = db.cf_handle(META_CF_NAME).unwrap();
info!(
"Active paste count: {}",
db.iterator_cf(meta_cf, IteratorMode::Start).count()
);
}
_ => (),
if signal == SIGUSR1 {
let meta_cf = db.cf_handle(META_CF_NAME).unwrap();
info!(
"Active paste count: {}",
db.iterator_cf(meta_cf, IteratorMode::Start).count()
);
}
}
}

View File

@ -1,10 +1,9 @@
use std::fmt::{Display, Formatter};
use std::io::Cursor;
use std::sync::Arc;
use gloo_console::log;
use js_sys::{Array, Uint8Array};
use omegaupload_common::crypto::{open_in_place, Key};
use omegaupload_common::crypto::{open_in_place, Error, Key};
use omegaupload_common::secrecy::{Secret, SecretVec};
use serde::Serialize;
use wasm_bindgen::JsCast;
@ -36,11 +35,10 @@ fn now() -> f64 {
pub fn decrypt(
mut container: Vec<u8>,
key: Secret<Key>,
key: &Secret<Key>,
maybe_password: Option<SecretVec<u8>>,
) -> Result<DecryptedData, PasteCompleteConstructionError> {
open_in_place(&mut container, &key, maybe_password)
.map_err(|_| PasteCompleteConstructionError::Decryption)?;
) -> Result<DecryptedData, Error> {
open_in_place(&mut container, key, maybe_password)?;
let mime_type = tree_magic_mini::from_u8(&container);
log!("Mimetype: ", mime_type);
@ -63,15 +61,22 @@ pub fn decrypt(
log!(format!("Blob conversion completed in {}ms", now() - start));
if mime_type.starts_with("text/") {
String::from_utf8(container)
.map(Arc::new)
.map(DecryptedData::String)
.map_err(|_| PasteCompleteConstructionError::InvalidEncoding)
} else if mime_type.starts_with("image/") || mime_type == "application/x-riff" {
if let Ok(string) = String::from_utf8(container) {
Ok(DecryptedData::String(Arc::new(string)))
} else {
Ok(DecryptedData::Blob(blob))
}
} else if mime_type.starts_with("image/")
// application/x-riff is WebP
|| mime_type == "application/x-riff"
{
Ok(DecryptedData::Image(blob, container.len()))
} else if mime_type.starts_with("audio/") {
Ok(DecryptedData::Audio(blob))
} else if mime_type.starts_with("video/") || mime_type == "application/x-matroska" {
} else if mime_type.starts_with("video/")
// application/x-matroska is mkv
|| mime_type == "application/x-matroska"
{
Ok(DecryptedData::Video(blob))
} else if mime_type == "application/zip" {
let mut entries = vec![];
@ -103,25 +108,3 @@ pub fn decrypt(
Ok(DecryptedData::Blob(blob))
}
}
#[derive(Debug)]
pub enum PasteCompleteConstructionError {
Decryption,
InvalidEncoding,
}
impl std::error::Error for PasteCompleteConstructionError {}
impl Display for PasteCompleteConstructionError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PasteCompleteConstructionError::Decryption => {
write!(f, "Failed to decrypt data.")
}
PasteCompleteConstructionError::InvalidEncoding => write!(
f,
"Got an file with a text/* mime type, but was unable to parsed as valid UTF-8?"
),
}
}
}

View File

@ -9,7 +9,7 @@ use gloo_console::{error, log};
use http::uri::PathAndQuery;
use http::{StatusCode, Uri};
use js_sys::{Array, JsString, Object, Uint8Array};
use omegaupload_common::crypto::Key;
use omegaupload_common::crypto::{Error as CryptoError, Key};
use omegaupload_common::secrecy::{Secret, SecretVec};
use omegaupload_common::{Expiration, PartialParsedUrl};
use reqwasm::http::Request;
@ -89,10 +89,15 @@ fn main() {
loop {
let pw = window().prompt_with_message("A password is required to decrypt this paste:");
if let Ok(Some(password)) = pw {
if !password.is_empty() {
match pw {
Ok(Some(password)) if password.is_empty() => {
break Some(SecretVec::new(password.into_bytes()));
}
Err(_) => {
render_message("This paste requires a password.".into());
return;
}
_ => (),
}
}
} else {
@ -147,7 +152,22 @@ async fn fetch_resources(
return Ok(());
}
let decrypted = decrypt(data, key, password)?;
let decrypted = match decrypt(data, &key, password) {
Ok(data) => data,
Err(e) => {
let msg = match e {
CryptoError::Password => "The provided password was incorrect.",
CryptoError::SecretKey => "The secret key in the URL was incorrect.",
ref e => {
log!(format!("Bad kdf or corrupted blob: {}", e));
"An internal error occurred."
}
};
render_message(JsString::from(msg));
bail!(e);
}
};
let db_open_req = open_idb()?;
// On success callback
@ -192,7 +212,7 @@ async fn fetch_resources(
"entries",
JsValue::from(
entries
.into_iter()
.iter()
.filter_map(|x| JsValue::from_serde(x).ok())
.collect::<Array>(),
),